diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dae954a0970b7..92a51eb84a4ab 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,7 +16,7 @@ The Magento 2 development team will review all issues and contributions submitte 4. PRs which include bug fixes must be accompanied with a step-by-step description of how to reproduce the bug. 3. PRs which include new logic or new features must be submitted along with: * Unit/integration test coverage -* Proposed [documentation](http://devdocs.magento.com) updates. Documentation contributions can be submitted via the [devdocs GitHub](https://github.com/magento/devdocs). +* Proposed [documentation](https://devdocs.magento.com) updates. Documentation contributions can be submitted via the [devdocs GitHub](https://github.com/magento/devdocs). 4. For larger features or changes, please [open an issue](https://github.com/magento/magento2/issues) to discuss the proposed changes prior to development. This may prevent duplicate or unnecessary effort and allow other contributors to provide input. 5. All automated tests must pass (all builds on [Travis CI](https://travis-ci.org/magento/magento2) must be green). @@ -27,7 +27,7 @@ If you are a new GitHub user, we recommend that you create your own [free github 1. Search current [listed issues](https://github.com/magento/magento2/issues) (open or closed) for similar proposals of intended contribution before starting work on a new contribution. 2. Review the [Contributor License Agreement](https://magento.com/legaldocuments/mca) if this is your first time contributing. 3. Create and test your work. -4. Fork the Magento 2 repository according to the [Fork A Repository instructions](http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#pull_request). +4. Fork the Magento 2 repository according to the [Fork A Repository instructions](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#pull_request). 5. Once your contribution is received the Magento 2 development team will review the contribution and collaborate with you as needed. ## Code of Conduct diff --git a/.travis.yml b/.travis.yml index bd2b94865cafd..d29aa241b15b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,6 +64,6 @@ script: # The scripts for grunt/phpunit type tests - if [ $TEST_SUITE == "functional" ]; then dev/tests/functional/vendor/phpunit/phpunit/phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER; fi - - if [ $TEST_SUITE != "functional" ] && [ $TEST_SUITE != "js"] && [ $TEST_SUITE != "graphql-api-functional" ]; then phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER; fi + - if [ $TEST_SUITE != "functional" ] && [ $TEST_SUITE != "js" ] && [ $TEST_SUITE != "graphql-api-functional" ]; then phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER; fi - if [ $TEST_SUITE == "js" ]; then grunt $GRUNT_COMMAND; fi - if [ $TEST_SUITE == "graphql-api-functional" ]; then phpunit -c dev/tests/api-functional; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index b86c7b79a0cbd..322cd10d0ff08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ To get detailed information about changes in Magento 2.3.0, see the [Release Not 2.1.0 ============= -To get detailed information about changes in Magento 2.1.0, please visit [Magento Community Edition (CE) Release Notes](http://devdocs.magento.com/guides/v2.1/release-notes/ReleaseNotes2.1.0CE.html "Magento Community Edition (CE) Release Notes") +To get detailed information about changes in Magento 2.1.0, please visit [Magento Community Edition (CE) Release Notes](https://devdocs.magento.com/guides/v2.1/release-notes/ReleaseNotes2.1.0CE.html "Magento Community Edition (CE) Release Notes") 2.0.0 ============= @@ -1025,7 +1025,7 @@ Tests: * Improved backend menu keyboard accessibility * Accessibility improvements: WAI-ARIA in a product item on a category page and related products * Checkout flow code can work with a separate DB storage - * Unit tests moved to module directories + * Unit tests moved to module directories * Addressed naming inconsistencies in REST routes * Added Advanced Developer workflow for frontend developers * Setup diff --git a/README.md b/README.md index 979e60a167748..e73da84d66f46 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,17 @@ Magento is thankful for any contribution that can improve our code base, documen -

Labels applied by the Magento team

-We apply labels to public Pull Requests and Issues to help other participants retrieve additional information about current progress, component assignments, Magento release lines, and much more. -Please review the Code Contributions guide for detailed information on labels used in Magento 2 repositories. +### Labels applied by the Magento team +We apply labels to public Pull Requests and Issues to help other participants retrieve additional information about current progress, component assignments, Magento release lines, and much more. +Please review the [Code Contributions guide](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#labels) for detailed information on labels used in Magento 2 repositories. -

Reporting security issues

+## Reporting security issues -To report security vulnerabilities in Magento software or web sites, please create a Bugcrowd researcher account there to submit and follow-up your issue. Learn more about reporting security issues here. +To report security vulnerabilities in Magento software or web sites, please create a Bugcrowd researcher account [there](https://bugcrowd.com/magento) to submit and follow-up your issue. Learn more about reporting security issues [here](https://magento.com/security/reporting-magento-security-issue). -Stay up-to-date on the latest security news and patches for Magento by signing up for Security Alert Notifications. +Stay up-to-date on the latest security news and patches for Magento by signing up for [Security Alert Notifications](https://magento.com/security/sign-up). -

License

+## License Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license. @@ -59,3 +59,13 @@ Please see [LICENSE.txt](https://github.com/magento/magento2/blob/2.3-develop/LI Subject to Licensee's payment of fees and compliance with the terms and conditions of the MEE License, the MEE License supersedes the OSL 3.0 license for each source file. Please see LICENSE_EE.txt for the full text of the MEE License or visit https://magento.com/legal/terms/enterprise. +## Community Engineering Slack + +To connect with Magento and the Community, join us on the [Magento Community Engineering Slack](https://magentocommeng.slack.com). If you are interested in joining Slack, or a specific channel, send us request at [engcom@adobe.com](mailto:engcom@adobe.com) or [self signup](https://tinyurl.com/engcom-slack). + + +We have channels for each project. These channels are recommended for new members: + +- [general](https://magentocommeng.slack.com/messages/C4YS78WE6): Open chat for introductions and Magento 2 questions +- [github](https://magentocommeng.slack.com/messages/C7KB93M32): Support for GitHub issues, pull requests, and processes +- [public-backlog](https://magentocommeng.slack.com/messages/CCV3J3RV5): Discussions of the Magento 2 backlog diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php index a92eebfaec510..40b089d947136 100644 --- a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php +++ b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php @@ -6,6 +6,8 @@ namespace Magento\AdminNotification\Model\ResourceModel; /** + * Inbox resource model + * * @api * @since 100.0.2 */ @@ -77,8 +79,7 @@ public function getNoticeStatus(\Magento\AdminNotification\Model\Inbox $object) 'is_read=?', 0 ); - $return = $connection->fetchPairs($select); - return $return; + return $connection->fetchPairs($select); } /** diff --git a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php index 38477c015cad3..c08b13373aa8d 100644 --- a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php +++ b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php @@ -7,6 +7,8 @@ namespace Magento\AdminNotification\Model\System\Message\Media\Synchronization; /** + * Media synchronization error message class. + * * @api * @since 100.0.2 */ @@ -27,7 +29,7 @@ class Error extends \Magento\AdminNotification\Model\System\Message\Media\Abstra protected function _shouldBeDisplayed() { $data = $this->_syncFlag->getFlagData(); - return isset($data['has_errors']) && true == $data['has_errors']; + return !empty($data['has_errors']); } /** diff --git a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php index 81bea14099e9e..ed882a0776734 100644 --- a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php +++ b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php @@ -6,6 +6,8 @@ namespace Magento\AdminNotification\Model\System\Message\Media\Synchronization; /** + * Media synchronization success message class. + * * @api * @since 100.0.2 */ @@ -27,8 +29,8 @@ protected function _shouldBeDisplayed() { $state = $this->_syncFlag->getState(); $data = $this->_syncFlag->getFlagData(); - $hasErrors = isset($data['has_errors']) && true == $data['has_errors'] ? true : false; - return false == $hasErrors && \Magento\MediaStorage\Model\File\Storage\Flag::STATE_FINISHED == $state; + $hasErrors = !empty($data['has_errors']); + return !$hasErrors && \Magento\MediaStorage\Model\File\Storage\Flag::STATE_FINISHED == $state; } /** diff --git a/app/code/Magento/Analytics/Model/Cryptographer.php b/app/code/Magento/Analytics/Model/Cryptographer.php index 665d564814b14..efddcb501aabb 100644 --- a/app/code/Magento/Analytics/Model/Cryptographer.php +++ b/app/code/Magento/Analytics/Model/Cryptographer.php @@ -129,7 +129,12 @@ private function getInitializationVector() */ private function validateCipherMethod($cipherMethod) { - $methods = openssl_get_cipher_methods(); + $methods = array_map( + 'strtolower', + openssl_get_cipher_methods() + ); + $cipherMethod = strtolower($cipherMethod); + return (false !== array_search($cipherMethod, $methods)); } } diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md index 7ec64abcd9b86..f600f1d406e4c 100644 --- a/app/code/Magento/Analytics/README.md +++ b/app/code/Magento/Analytics/README.md @@ -1,6 +1,6 @@ # Magento_Analytics Module -The Magento_Analytics module integrates your Magento instance with the [Magento Business Intelligence (MBI)](https://magento.com/products/business-intelligence) to use [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html) functionality. +The Magento_Analytics module integrates your Magento instance with the [Magento Business Intelligence (MBI)](https://magento.com/products/business-intelligence) to use [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html) functionality. The module implements the following functionality: @@ -16,8 +16,8 @@ The module implements the following functionality: ## Structure -Beyond the [usual module file structure](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) the module contains a directory `ReportXml`. -[Report XML](http://devdocs.magento.com/guides/v2.2/advanced-reporting/report-xml.html) is a markup language used to build reports for Advanced Reporting. +Beyond the [usual module file structure](https://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) the module contains a directory `ReportXml`. +[Report XML](https://devdocs.magento.com/guides/v2.2/advanced-reporting/report-xml.html) is a markup language used to build reports for Advanced Reporting. The language declares SQL queries using XML declaration. ## Subscription Process diff --git a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php index 14b80c6814ba6..3af168886a447 100644 --- a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php +++ b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php @@ -76,7 +76,7 @@ public function getColumns(SelectBuilder $selectBuilder, $entityConfig) $columnName = $this->nameResolver->getName($attributeData); if (isset($attributeData['function'])) { $prefix = ''; - if (isset($attributeData['distinct']) && $attributeData['distinct'] == true) { + if (!empty($attributeData['distinct'])) { $prefix = ' DISTINCT '; } $expression = new ColumnValueExpression( diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php index 47747027ed702..cf00556cfe590 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php @@ -191,7 +191,7 @@ public function testPrepareExportData($isArchiveSourceDirectory) ->with( $archiveSource, $archiveAbsolutePath, - $isArchiveSourceDirectory ? true : false + $isArchiveSourceDirectory ); $fileContent = 'Some text'; @@ -222,7 +222,7 @@ public function prepareExportDataDataProvider() { return [ 'Data source for archive is directory' => [true], - 'Data source for archive doesn\'t directory' => [false], + 'Data source for archive isn\'t directory' => [false], ]; } diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index a409251aa76ef..aeaa0cd9a3ad1 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -707,6 +707,7 @@ protected function processOrder(\Magento\Sales\Model\Order $order) //decline the order (in case of wrong response code) but don't return money to customer. $message = $e->getMessage(); $this->declineOrder($order, $message, false); + throw $e; } @@ -777,7 +778,7 @@ protected function processPaymentFraudStatus(\Magento\Sales\Model\Order\Payment } /** - * Add status comment + * Add status comment to history * * @param \Magento\Sales\Model\Order\Payment $payment * @return $this @@ -832,6 +833,7 @@ protected function declineOrder(\Magento\Sales\Model\Order $order, $message = '' ->void($response); } $order->registerCancellation($message)->save(); + $this->_eventManager->dispatch('order_cancel_after', ['order' => $order ]); } catch (\Exception $e) { //quiet decline $this->getPsrLogger()->critical($e); @@ -866,7 +868,7 @@ public function getConfigInterface() * Getter for specified value according to set payment method code * * @param mixed $key - * @param int|string|null|\Magento\Store\Model\Store $storeId + * @param mixed $storeId * @return mixed */ public function getValue($key, $storeId = null) @@ -930,7 +932,7 @@ public function fetchTransactionInfo(\Magento\Payment\Model\InfoInterface $payme } /** - * Add statuc comment on update. + * Add status comment on update * * @param \Magento\Sales\Model\Order\Payment $payment * @param \Magento\Framework\DataObject $response diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml index 3a192646b6f7e..a960e96a288ea 100644 --- a/app/code/Magento/Authorizenet/etc/config.xml +++ b/app/code/Magento/Authorizenet/etc/config.xml @@ -33,6 +33,7 @@ https://apitest.authorize.net/xml/v1/request.api https://api2.authorize.net/xml/v1/request.api x_card_type,x_account_number,x_avs_code,x_auth_code,x_response_reason_text,x_cvv2_resp_code + authorizenet diff --git a/app/code/Magento/Authorizenet/etc/payment.xml b/app/code/Magento/Authorizenet/etc/payment.xml new file mode 100644 index 0000000000000..8f92d5fba3aef --- /dev/null +++ b/app/code/Magento/Authorizenet/etc/payment.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php index eff49c3b75ab2..d599d5fbad5e0 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php @@ -4,14 +4,13 @@ * See COPYING.txt for license details. */ +namespace Magento\Backend\Block\Widget\Form\Element; + /** * Form element dependencies mapper * Assumes that one element may depend on other element values. * Will toggle as "enabled" only if all elements it depends from toggle as true. - */ -namespace Magento\Backend\Block\Widget\Form\Element; - -/** + * * @api * @since 100.0.2 */ @@ -117,6 +116,7 @@ public function addConfigOptions(array $options) /** * HTML output getter + * * @return string */ protected function _toHtml() @@ -139,7 +139,8 @@ protected function _toHtml() } /** - * Field dependences JSON map generator + * Field dependencies JSON map generator + * * @return string */ protected function _getDependsJson() diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php index 185b1116b8f67..9890a10a4ceb0 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -7,6 +7,7 @@ namespace Magento\Backend\Block\Widget\Grid\Massaction; use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker; +use Magento\Framework\Data\Collection\AbstractDb; use Magento\Framework\DataObject; /** @@ -52,7 +53,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -217,6 +218,7 @@ public function getGridJsObjectName() * Retrieve JSON string of selected checkboxes * * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getSelectedJson() { @@ -231,6 +233,7 @@ public function getSelectedJson() * Retrieve array of selected checkboxes * * @return string[] + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getSelected() { @@ -252,6 +255,8 @@ public function getApplyButtonHtml() } /** + * Get mass action javascript code. + * * @return string */ public function getJavaScript() @@ -268,6 +273,8 @@ public function getJavaScript() } /** + * Get grid ids in JSON format. + * * @return string */ public function getGridIdsJson() @@ -284,6 +291,11 @@ public function getGridIdsJson() $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } + if ($allIdsCollection instanceof AbstractDb) { + $allIdsCollection->getSelect()->limit(); + $allIdsCollection->clear(); + } + $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); if (!empty($gridIds)) { return join(",", $gridIds); @@ -292,6 +304,8 @@ public function getGridIdsJson() } /** + * Get Html id. + * * @return string */ public function getHtmlId() diff --git a/app/code/Magento/Backend/Model/Search/Customer.php b/app/code/Magento/Backend/Model/Search/Customer.php index 35a7359ce9980..e76a1b77ab2d6 100644 --- a/app/code/Magento/Backend/Model/Search/Customer.php +++ b/app/code/Magento/Backend/Model/Search/Customer.php @@ -89,7 +89,7 @@ public function load() $this->searchCriteriaBuilder->setCurrentPage($this->getStart()); $this->searchCriteriaBuilder->setPageSize($this->getLimit()); - $searchFields = ['firstname', 'lastname', 'company']; + $searchFields = ['firstname', 'lastname', 'billing_company']; $filters = []; foreach ($searchFields as $field) { $filters[] = $this->filterBuilder diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml index a7ef237a232b8..1070bc409962a 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml @@ -13,10 +13,9 @@ - - \ No newline at end of file + diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml index bc576559e7a13..ef2e5f4216889 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml index b1350d5dcc1d7..88e740d689cdd 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml @@ -12,5 +12,6 @@ + diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml new file mode 100644 index 0000000000000..a01e025ba3dca --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml @@ -0,0 +1,16 @@ + + + +
+ + + + +
+
diff --git a/app/code/Magento/Backend/etc/module.xml b/app/code/Magento/Backend/etc/module.xml index 3a5cd8226753d..03976396f6fd5 100644 --- a/app/code/Magento/Backend/etc/module.xml +++ b/app/code/Magento/Backend/etc/module.xml @@ -9,6 +9,7 @@ + diff --git a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml index 93309c9a22ef2..b0abec3aa9bec 100644 --- a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml +++ b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml @@ -6,6 +6,7 @@ */ --> + diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml index c2e6af9dcd735..412513c59c63c 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml @@ -11,26 +11,26 @@ - + - + - + - + - + - + diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml index 65eddc0d9e51a..4d531214db150 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml @@ -6,15 +6,15 @@ */ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - - - - + + + + @@ -23,6 +23,5 @@ - - \ No newline at end of file + diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml index 772c1c39a04ca..30345ec31bacd 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml @@ -18,6 +18,7 @@ Yerevan 9999 9999 + Armenia diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml index 2b5d60bea24e1..f27477ce8a672 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -65,7 +65,7 @@ - diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml index d1d685effd133..cf51f29db79f9 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml @@ -11,7 +11,7 @@ - + <description value="Admin should be able to open a New Credit Memo Page from Invoice Page for Order with tax and discount and payed using online payment method"/> <severity value="CRITICAL"/> @@ -72,9 +72,9 @@ <!-- Create a cart price rule with 10% discount for whole cart --> <click selector="{{AdminMenuSection.marketing}}" stepKey="clickOnMarketing" /> - <waitForPageLoad stepKey="waitForMarketing" time="3"/> + <waitForPageLoad stepKey="waitForMarketing"/> <click selector="{{CartPriceRulesSubmenuSection.cartPriceRules}}" stepKey="clickOnCartPriceRules"/> - <waitForPageLoad stepKey="waitForCartPriceRules" time="3"/> + <waitForPageLoad stepKey="waitForCartPriceRules"/> <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> @@ -83,7 +83,7 @@ <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Percent of product price discount" stepKey="selectActionType"/> <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="10" stepKey="fillDiscountAmount"/> <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> - <waitForPageLoad stepKey="waitForCartRuleLoad" time="3"/> + <waitForPageLoad stepKey="waitForCartRuleLoad"/> <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> <!--Set Taxable Goods for Shipping Tax Class--> @@ -112,7 +112,7 @@ <!--Submit Order--> <click stepKey="submitOrder" selector="{{NewOrderSection.submitOrder}}"/> - <waitForPageLoad stepKey="waitForSubmitOrder" time="5"/> + <waitForPageLoad stepKey="waitForSubmitOrder"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeOrderSuccessMessage" after="waitForSubmitOrder"/> <!-- Create New invoice--> @@ -120,7 +120,7 @@ <!--Get access to Credit Memo page from Invoice page--> <click selector="{{AdminInvoiceMainActionsSection.openNewCreditMemoFromInvoice}}" stepKey="clickCreateNewCreditMemo"/> - <waitForPageLoad stepKey="waitForLoadNewCreditMemoPage" time="5"/> + <waitForPageLoad stepKey="waitForLoadNewCreditMemoPage"/> <see selector="{{AdminCreditMemoOrderInformationSection.orderStatus}}" userInput="Processing" stepKey="seeNewCreditMemo"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index a830c29368755..9de4773af023a 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -42,6 +42,7 @@ <paymentInfoKeys>cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision</paymentInfoKeys> <avs_ems_adapter>Magento\Braintree\Model\AvsEmsCodeMapper</avs_ems_adapter> <cvv_ems_adapter>Magento\Braintree\Model\CvvEmsCodeMapper</cvv_ems_adapter> + <group>braintree</group> </braintree> <braintree_paypal> <model>BraintreePayPalFacade</model> @@ -67,6 +68,7 @@ <privateInfoKeys>processorResponseCode,processorResponseText,paymentId</privateInfoKeys> <paymentInfoKeys>processorResponseCode,processorResponseText,paymentId,payerEmail</paymentInfoKeys> <supported_locales>en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW</supported_locales> + <group>braintree</group> </braintree_paypal> <braintree_cc_vault> <model>BraintreeCreditCardVaultFacade</model> @@ -76,6 +78,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> + <group>braintree</group> </braintree_cc_vault> <braintree_paypal_vault> <model>BraintreePayPalVaultFacade</model> @@ -85,6 +88,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> + <group>braintree</group> </braintree_paypal_vault> </payment> </default> diff --git a/app/code/Magento/Braintree/etc/payment.xml b/app/code/Magento/Braintree/etc/payment.xml new file mode 100644 index 0000000000000..dbabd91151022 --- /dev/null +++ b/app/code/Magento/Braintree/etc/payment.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/payment.xsd"> + <groups> + <group id="braintree"> + <label>Braintree</label> + </group> + </groups> +</payment> diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml index 535a5a852fe70..4c15fffa8189f 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml @@ -83,7 +83,7 @@ $ccType = $block->getInfoData('cc_type'); id="<?= /* @noEscape */ $code ?>_vault" name="payment[is_active_payment_token_enabler]" class="admin__control-checkbox"/> - <label class="label" for="<?= /* @noEscape */ $code ?>_vault"> + <label class="label admin__field-label" for="<?= /* @noEscape */ $code ?>_vault"> <span><?= $block->escapeHtml(__('Save for later use.')) ?></span> </label> </div> diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Crosssell.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Crosssell.php index ba7b9b5fcb593..08fac66c4a4fe 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Crosssell.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Crosssell.php @@ -6,6 +6,16 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; -class Crosssell extends \Magento\Catalog\Controller\Adminhtml\Product\Crosssell +use Magento\Catalog\Controller\Adminhtml\Product\Crosssell as CatalogCrossel; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Crosssell + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class Crosssell extends CatalogCrossel implements HttpPostActionInterface { } diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/CrosssellGrid.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/CrosssellGrid.php index 87550d768ca8e..b301d6ee2fea9 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/CrosssellGrid.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/CrosssellGrid.php @@ -6,6 +6,16 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; -class CrosssellGrid extends \Magento\Catalog\Controller\Adminhtml\Product\CrosssellGrid +use Magento\Catalog\Controller\Adminhtml\Product\CrosssellGrid as CatalogCrosssellGrid; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class CrosssellGrid + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class CrosssellGrid extends CatalogCrosssellGrid implements HttpPostActionInterface { } diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Related.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Related.php index 58f464fba3afe..7534cfddcaadf 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Related.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Related.php @@ -6,6 +6,16 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; -class Related extends \Magento\Catalog\Controller\Adminhtml\Product\Related +use Magento\Catalog\Controller\Adminhtml\Product\Related as CatalogRelated; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Related + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class Related extends CatalogRelated implements HttpPostActionInterface { } diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/RelatedGrid.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/RelatedGrid.php index a27efe8bc2b68..e0fc9226663f4 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/RelatedGrid.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/RelatedGrid.php @@ -6,6 +6,16 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; -class RelatedGrid extends \Magento\Catalog\Controller\Adminhtml\Product\RelatedGrid +use Magento\Catalog\Controller\Adminhtml\Product\RelatedGrid as CatalogRelatedGrid; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class RelatedGrid + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class RelatedGrid extends CatalogRelatedGrid implements HttpPostActionInterface { } diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Upsell.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Upsell.php index d0a6343569ee8..239b13970e696 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Upsell.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/Upsell.php @@ -6,6 +6,16 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; -class Upsell extends \Magento\Catalog\Controller\Adminhtml\Product\Upsell +use Magento\Catalog\Controller\Adminhtml\Product\Upsell as CatalogUpsell; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Upsell + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ +class Upsell extends CatalogUpsell implements HttpPostActionInterface { } diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/UpsellGrid.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/UpsellGrid.php index 74c87be5b7108..ed3312d3b0734 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/UpsellGrid.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/UpsellGrid.php @@ -6,6 +6,13 @@ */ namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; +/** + * Class UpsellGrid + * + * @package Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ class UpsellGrid extends \Magento\Catalog\Controller\Adminhtml\Product\UpsellGrid { } diff --git a/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php b/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php new file mode 100644 index 0000000000000..499f0cd2ca9c5 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Plugin\Frontend; + +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Model\Product as CatalogProduct; + +/** + * Add child identities to product identities on storefront. + */ +class Product +{ + /** + * @var Type + */ + private $type; + + /** + * @param Type $type + */ + public function __construct(Type $type) + { + $this->type = $type; + } + + /** + * Add child identities to product identities + * + * @param CatalogProduct $product + * @param array $identities + * @return array + */ + public function afterGetIdentities(CatalogProduct $product, array $identities): array + { + foreach ($this->type->getChildrenIds($product->getEntityId()) as $childIds) { + foreach ($childIds as $childId) { + $identities[] = CatalogProduct::CACHE_TAG . '_' . $childId; + } + } + + return array_unique($identities); + } +} diff --git a/app/code/Magento/Bundle/Model/Product/SaveHandler.php b/app/code/Magento/Bundle/Model/Product/SaveHandler.php index fc215aa6b8e20..99e8188146bbb 100644 --- a/app/code/Magento/Bundle/Model/Product/SaveHandler.php +++ b/app/code/Magento/Bundle/Model/Product/SaveHandler.php @@ -58,6 +58,8 @@ public function __construct( } /** + * Perform action on Bundle product relation/extension attribute + * * @param object $entity * @param array $arguments * @@ -83,7 +85,7 @@ public function execute($entity, $arguments = []) : []; if (!$entity->getCopyFromView()) { - $this->processRemovedOptions($entity->getSku(), $existingOptionsIds, $optionIds); + $this->processRemovedOptions($entity, $existingOptionsIds, $optionIds); $newOptionsIds = array_diff($optionIds, $existingOptionsIds); $this->saveOptions($entity, $bundleProductOptions, $newOptionsIds); } else { @@ -96,6 +98,8 @@ public function execute($entity, $arguments = []) } /** + * Remove option product links + * * @param string $entitySku * @param \Magento\Bundle\Api\Data\OptionInterface $option * @return void @@ -154,16 +158,19 @@ private function getOptionIds(array $options): array /** * Removes old options that no longer exists. * - * @param string $entitySku + * @param ProductInterface $entity * @param array $existingOptionsIds * @param array $optionIds * @return void */ - private function processRemovedOptions(string $entitySku, array $existingOptionsIds, array $optionIds): void + private function processRemovedOptions(ProductInterface $entity, array $existingOptionsIds, array $optionIds): void { + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $parentId = $entity->getData($metadata->getLinkField()); foreach (array_diff($existingOptionsIds, $optionIds) as $optionId) { - $option = $this->optionRepository->get($entitySku, $optionId); - $this->removeOptionLinks($entitySku, $option); + $option = $this->optionRepository->get($entity->getSku(), $optionId); + $option->setParentId($parentId); + $this->removeOptionLinks($entity->getSku(), $option); $this->optionRepository->delete($option); } } diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index 641f4490874da..2dc519dbf1540 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -13,6 +13,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\ArrayUtils; /** * Bundle Type Model @@ -160,6 +161,11 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType */ private $selectionCollectionFilterApplier; + /** + * @var ArrayUtils + */ + private $arrayUtility; + /** * @param \Magento\Catalog\Model\Product\Option $catalogProductOption * @param \Magento\Eav\Model\Config $eavConfig @@ -185,6 +191,7 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType * @param \Magento\Framework\Serialize\Serializer\Json $serializer * @param MetadataPool|null $metadataPool * @param SelectionCollectionFilterApplier|null $selectionCollectionFilterApplier + * @param ArrayUtils|null $arrayUtility * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -212,7 +219,8 @@ public function __construct( \Magento\CatalogInventory\Api\StockStateInterface $stockState, Json $serializer = null, MetadataPool $metadataPool = null, - SelectionCollectionFilterApplier $selectionCollectionFilterApplier = null + SelectionCollectionFilterApplier $selectionCollectionFilterApplier = null, + ArrayUtils $arrayUtility = null ) { $this->_catalogProduct = $catalogProduct; $this->_catalogData = $catalogData; @@ -232,6 +240,7 @@ public function __construct( $this->selectionCollectionFilterApplier = $selectionCollectionFilterApplier ?: ObjectManager::getInstance()->get(SelectionCollectionFilterApplier::class); + $this->arrayUtility= $arrayUtility ?: ObjectManager::getInstance()->get(ArrayUtils::class); parent::__construct( $catalogProductOption, @@ -673,7 +682,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p $options ); - $selectionIds = $this->multiToFlatArray($options); + $selectionIds = array_values($this->arrayUtility->flatten($options)); // If product has not been configured yet then $selections array should be empty if (!empty($selectionIds)) { $selections = $this->getSelectionsByIds($selectionIds, $product); @@ -814,26 +823,6 @@ private function recursiveIntval(array $array) return $array; } - /** - * Convert multi dimensional array to flat - * - * @param array $array - * @return int[] - */ - private function multiToFlatArray(array $array) - { - $flatArray = []; - foreach ($array as $value) { - if (is_array($value)) { - $flatArray = array_merge($flatArray, $this->multiToFlatArray($value)); - } else { - $flatArray[] = $value; - } - } - - return $flatArray; - } - /** * Retrieve message for specify option(s) * diff --git a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php index adb0777151b9e..04a6ee0bd459b 100644 --- a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php +++ b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php @@ -198,6 +198,8 @@ protected function getSelectionAmounts(Product $bundleProduct, $searchMin, $useR } /** + * Get selection price list provider. + * * @return SelectionPriceListProviderInterface * @deprecated 100.2.0 */ @@ -281,7 +283,7 @@ public function calculateBundleAmount($basePriceValue, $bundleProduct, $selectio * @param float $basePriceValue * @param Product $bundleProduct * @param \Magento\Bundle\Pricing\Price\BundleSelectionPrice[] $selectionPriceList - * @param null|bool|string|arrayy $exclude + * @param null|bool|string|array $exclude * @return \Magento\Framework\Pricing\Amount\AmountInterface */ protected function calculateFixedBundleAmount($basePriceValue, $bundleProduct, $selectionPriceList, $exclude) diff --git a/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php b/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php similarity index 94% rename from app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php rename to app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php index 44647ea76a1c2..701def7fc13d8 100644 --- a/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php +++ b/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php @@ -6,19 +6,19 @@ namespace Magento\Bundle\Setup\Patch\Data; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; -use Magento\Catalog\Api\Data\ProductAttributeInterface; -use Magento\Eav\Setup\EavSetup; /** - * Class UpdateBundleRelatedEntityTytpes + * Class UpdateBundleRelatedEntityTypes + * * @package Magento\Bundle\Setup\Patch */ -class UpdateBundleRelatedEntityTytpes implements DataPatchInterface, PatchVersionInterface +class UpdateBundleRelatedEntityTypes implements DataPatchInterface, PatchVersionInterface { /** * @var ModuleDataSetupInterface @@ -31,7 +31,7 @@ class UpdateBundleRelatedEntityTytpes implements DataPatchInterface, PatchVersio private $eavSetupFactory; /** - * UpdateBundleRelatedEntityTytpes constructor. + * UpdateBundleRelatedEntityTypes constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param EavSetupFactory $eavSetupFactory */ @@ -44,7 +44,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -177,7 +177,7 @@ private function upgradeShipmentType(EavSetup $eavSetup) } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -187,7 +187,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -195,7 +195,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml index 72e729111948f..d86d720ed7f5d 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml @@ -56,4 +56,74 @@ <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="50" stepKey="fillQuantity1"/> <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="50" stepKey="fillQuantity2"/> </actionGroup> + + <actionGroup name="addBundleOptionWithOneProduct" extends="addBundleOptionWithTwoProducts"> + <remove keyForRemoval="openProductFilters2"/> + <remove keyForRemoval="fillProductSkuFilter2"/> + <remove keyForRemoval="clickApplyFilters2"/> + <remove keyForRemoval="waitForFilteredGridLoad2"/> + <remove keyForRemoval="selectProduct2"/> + <remove keyForRemoval="selectProduct2"/> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="1" stepKey="fillQuantity" after="clickAddButton1"/> + </actionGroup> + + <actionGroup name="addBundleOptionWithTreeProducts" extends="addBundleOptionWithTwoProducts"> + <arguments> + <argument name="prodTreeSku" type="string"/> + </arguments> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters3" after="selectProduct2"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters3" after="clickClearFilters3"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodTreeSku}}" stepKey="fillProductSkuFilter3" after="openProductFilters3"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters3" after="fillProductSkuFilter3"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad3" time="30" after="clickApplyFilters3"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct3" after="waitForFilteredGridLoad3"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="1" stepKey="fillQuantity1" after="clickAddButton1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="1" stepKey="fillQuantity2" after="fillQuantity1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '2')}}" userInput="1" stepKey="fillQuantity3" after="fillQuantity2"/> + </actionGroup> + + <actionGroup name="addBundleOptionWithSixProducts" extends="addBundleOptionWithTwoProducts"> + <arguments> + <argument name="prodTreeSku" type="string"/> + <argument name="prodFourSku" type="string"/> + <argument name="prodFiveSku" type="string"/> + <argument name="prodSixSku" type="string"/> + </arguments> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters3" after="selectProduct2"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters3" after="clickClearFilters3"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodTreeSku}}" stepKey="fillProductSkuFilter3" after="openProductFilters3"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters3" after="fillProductSkuFilter3"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad3" time="30" after="clickApplyFilters3"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct3" after="waitForFilteredGridLoad3"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters4" after="selectProduct3"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters4" after="clickClearFilters4"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodFourSku}}" stepKey="fillProductSkuFilter4" after="openProductFilters4"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters4" after="fillProductSkuFilter4"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad4" time="30" after="clickApplyFilters4"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct4" after="clickApplyFilters4"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters5" after="selectProduct4"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters5" after="clickClearFilters5"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodFiveSku}}" stepKey="fillProductSkuFilter5" after="openProductFilters5"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters5" after="fillProductSkuFilter5"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad5" time="30" after="clickApplyFilters5"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct5" after="waitForFilteredGridLoad5"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters6" after="selectProduct5"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters6" after="clickClearFilters6"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodSixSku}}" stepKey="fillProductSkuFilter6" after="openProductFilters6"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters6" after="fillProductSkuFilter6"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad6" time="30" after="clickApplyFilters6"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct6" after="waitForFilteredGridLoad6"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="2" stepKey="fillQuantity1" after="clickAddButton1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="2" stepKey="fillQuantity2" after="fillQuantity1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '2')}}" userInput="2" stepKey="fillQuantity3" after="fillQuantity2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '3')}}" userInput="2" stepKey="fillQuantity4" after="fillQuantity3"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '4')}}" userInput="2" stepKey="fillQuantity5" after="fillQuantity4"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '5')}}" userInput="2" stepKey="fillQuantity6" after="fillQuantity5"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index f28ffbdc40acc..e36730a87b41a 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -24,4 +24,18 @@ <waitForPageLoad time="30" stepKey="waitForPageLoad3"/> <waitForText userInput="{{quantity}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> </actionGroup> + + <!-- Add Bundle Product to Cart from the category page --> + <actionGroup name="StorefrontAddBundleProductFromCategoryToCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductTitleByName(productName)}}" stepKey="moveMouseOverProduct"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName(productName)}}" stepKey="openProductPage"/> + <waitForPageLoad time="30" stepKey="waitForBundleProductPageLoad"/> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomizeAndAddToCart"/> + <click selector="{{StorefrontBundledSection.addToCartConfigured}}" stepKey="clickAddBundleProductToCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.productCount}}" stepKey="waitProductCount"/> + <see userInput="You added {{productName}} to your shopping cart." selector="{{StorefrontMessagesSection.success}}" stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml new file mode 100644 index 0000000000000..5cd286c0c6aa1 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="BundleProductsSummary" type="Quote"> + <data key="subtotal">1,968.00</data> + <data key="shipping">5.00</data> + <data key="total">1,973.00</data> + <data key="shippingMethod">Flat Rate - Fixed</data> + </entity> +</entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index 814d03c52f4be..516f40ac2e7b7 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -22,6 +22,8 @@ <element name="bundleOptionXProductYQuantity" type="input" selector="[name='bundle_options[bundle_options][{{x}}][bundle_selections][{{y}}][selection_qty]']" parameterized="true"/> <element name="addProductsToOption" type="button" selector="[data-index='modal_set']" timeout="30"/> <element name="nthAddProductsToOption" type="button" selector="//tr[{{var}}]//button[@data-index='modal_set']" timeout="30" parameterized="true"/> + <element name="bundlePriceType" type="select" selector="bundle_options[bundle_options][0][bundle_selections][0][selection_price_type]"/> + <element name="bundlePriceValue" type="input" selector="bundle_options[bundle_options][0][bundle_selections][0][selection_price_value]"/> <!--Select"url Key"InputForm--> <element name="urlKey" type="input" selector="//input[@name='product[url_key]']" timeout="30"/> <!--AddSelectedProducts--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index 946992f1efe04..dbe48c46c820b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -9,6 +9,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBundledSection"> + <element name="productCheckbox" type="select" selector="//*[@id='customizeTitle']/following-sibling::div[{{arg1}}]//div[{{arg2}}][@class='field choice']/input" parameterized="true"/> + <element name="bundleProductsPrice" type="text" selector="//*[@class='bundle-info']//*[contains(@id,'product-price')]/span"/> <element name="nthBundledOption" type="input" selector=".option:nth-of-type({{numOption}}) .choice:nth-of-type({{numOptionSelect}}) input" parameterized="true"/> <element name="addToCart" type="button" selector="#bundle-slide" timeout="30"/> <element name="addToCartConfigured" type="button" selector="#product-addtocart-button" timeout="30"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml new file mode 100644 index 0000000000000..6ea5a2842470b --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddBundleOptionsToCartTest"> + <annotations> + <features value="Bundle"/> + <stories value="MAGETWO-95813: Only two bundle options are added to the cart"/> + <title value="Checking adding of bundle options to the cart"/> + <description value="Verifying adding of bundle options to the cart"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95933"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteSimpleProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteSimpleProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteSimpleProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteSimpleProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteSimpleProduct10"/> + <!--delete created bundle product--> + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="{{BundleProduct.sku}}"/> + </actionGroup> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Start creating a bundle product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add Option One, a "Checkbox" type option, with tree products --> + <actionGroup ref="addBundleOptionWithTreeProducts" stepKey="addBundleOptionWithTreeProducts"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="prodTreeSku" value="$$simpleProduct3.sku$$"/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Add Option Two, a "Radio Buttons" type option, with one product --> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptionWithOneProduct"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$simpleProduct4.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option Two"/> + <argument name="inputType" value="radio"/> + </actionGroup> + + <!-- Add Option Tree, a "Checkbox" type option, with six products --> + <actionGroup ref="addBundleOptionWithSixProducts" stepKey="addBundleOptionWithSixProducts"> + <argument name="x" value="2"/> + <argument name="n" value="3"/> + <argument name="prodOneSku" value="$$simpleProduct5.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct6.sku$$"/> + <argument name="prodTreeSku" value="$$simpleProduct7.sku$$"/> + <argument name="prodFourSku" value="$$simpleProduct8.sku$$"/> + <argument name="prodFiveSku" value="$$simpleProduct9.sku$$"/> + <argument name="prodSixSku" value="$$simpleProduct10.sku$$"/> + <argument name="optionTitle" value="Option Tree"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Save product--> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Go to Storefront and open Bundle Product page--> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + + <!--Click "Customize and Add to Cart" button--> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!--Assert Bundle Product Price--> + <grabTextFrom selector="{{StorefrontBundledSection.bundleProductsPrice}}" stepKey="grabProductsPrice"/> + <assertEquals expected='$123.00' expectedType="string" actual="$grabProductsPrice" message="ExpectedPrice" stepKey="assertBundleProductPrice"/> + + <!--Chose all products from 1st & 3rd options --> + <click stepKey="selectProduct1" selector="{{StorefrontBundledSection.productCheckbox('1','1')}}"/> + <click stepKey="selectProduct2" selector="{{StorefrontBundledSection.productCheckbox('1','2')}}"/> + <click stepKey="selectProduct3" selector="{{StorefrontBundledSection.productCheckbox('1','3')}}"/> + <click stepKey="selectProduct5" selector="{{StorefrontBundledSection.productCheckbox('3','1')}}"/> + <click stepKey="selectProduct6" selector="{{StorefrontBundledSection.productCheckbox('3','2')}}"/> + <click stepKey="selectProduct7" selector="{{StorefrontBundledSection.productCheckbox('3','3')}}"/> + <click stepKey="selectProduct8" selector="{{StorefrontBundledSection.productCheckbox('3','4')}}"/> + <click stepKey="selectProduct9" selector="{{StorefrontBundledSection.productCheckbox('3','5')}}"/> + <click stepKey="selectProduct10" selector="{{StorefrontBundledSection.productCheckbox('3','6')}}"/> + + <!--Click "Add to Cart" button--> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickAddBundleProductToCart"/> + <waitForPageLoad time="30" stepKey="waitForAddBundleProductPageLoad"/> + + <!--Click "mini cart" icon--> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <waitForPageLoad stepKey="waitForDetailsOpen"/> + + <!--Check all products and Cart Subtotal --> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert" after="waitForDetailsOpen"> + <argument name="subtotal" value="BundleProductsSummary.subtotal"/> + <argument name="shipping" value="BundleProductsSummary.shipping"/> + <argument name="shippingMethod" value="BundleProductsSummary.shippingMethod"/> + <argument name="total" value="BundleProductsSummary.total"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml new file mode 100644 index 0000000000000..33181d6e920eb --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddBundleProductWithZeroPriceToShoppingCartTest"> + <annotations> + <features value="Bundle"/> + <stories value="Add Bundle product with zero price to shopping cart"/> + <title value="Add Bundle product with zero price to shopping cart"/> + <description value="Add Bundle product with zero price to shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-95167"/> + <group value="bundle"/> + </annotations> + <before> + <!--Enable freeShipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <!--Create category--> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple with zero price product--> + <createData entity="ApiProductWithDescription" stepKey="apiSimple"> + <field key="price">0</field> + </createData> + <!--Create Bundle product--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="apiBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <!--Create Attribute--> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="apiBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink"> + <requiredEntity createDataKey="apiBundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="apiSimple"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> + <deleteData createDataKey="apiSimple" stepKey="deleteSimple"/> + <deleteData createDataKey="apiBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Open category page--> + <amOnPage url="{{StorefrontCategoryPage.url($$createSubCategory.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryPage"/> + <!--Add bundle product to cart--> + <actionGroup ref="StorefrontAddBundleProductFromCategoryToCartActionGroup" stepKey="addBundleProductToCart"> + <argument name="productName" value="$$apiBundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <!--Place order--> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Free Shipping"/> + </actionGroup> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="checkoutPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!--Check subtotal in created order--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForAdminOrderPageLoad"/> + <scrollTo selector="{{AdminOrderTotalSection.subTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.subTotal}}" userInput="$0.00" stepKey="checkSubtotal"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php new file mode 100644 index 0000000000000..ee08618eab5dd --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Test\Unit\Model\Plugin\Frontend; + +use Magento\Bundle\Model\Plugin\Frontend\Product as ProductPlugin; +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Model\Product; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class ProductTest extends \PHPUnit\Framework\TestCase +{ + /** @var \Magento\Bundle\Model\Plugin\Product */ + private $plugin; + + /** @var MockObject|Type */ + private $type; + + /** @var MockObject|\Magento\Catalog\Model\Product */ + private $product; + + protected function setUp() + { + $this->product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getEntityId']) + ->getMock(); + + $this->type = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->setMethods(['getChildrenIds']) + ->getMock(); + + $this->plugin = new ProductPlugin($this->type); + } + + public function testAfterGetIdentities() + { + $baseIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + ]; + $id = 12345; + $childIds = [ + 1 => [1, 2, 5, 100500], + 12 => [7, 22, 45, 24612] + ]; + $expectedIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + Product::CACHE_TAG . '_' . 1, + Product::CACHE_TAG . '_' . 2, + Product::CACHE_TAG . '_' . 5, + Product::CACHE_TAG . '_' . 100500, + Product::CACHE_TAG . '_' . 7, + Product::CACHE_TAG . '_' . 22, + Product::CACHE_TAG . '_' . 45, + Product::CACHE_TAG . '_' . 24612, + ]; + $this->product->expects($this->once()) + ->method('getEntityId') + ->will($this->returnValue($id)); + $this->type->expects($this->once()) + ->method('getChildrenIds') + ->with($id) + ->will($this->returnValue($childIds)); + $identities = $this->plugin->afterGetIdentities($this->product, $baseIdentities); + $this->assertEquals($expectedIdentities, $identities); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php index 59f7f008ed3ee..9d7629c6f0a41 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php @@ -15,6 +15,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\ArrayUtils; /** * Class TypeTest @@ -87,6 +88,11 @@ class TypeTest extends \PHPUnit\Framework\TestCase */ private $serializer; + /** + * @var ArrayUtils|\PHPUnit_Framework_MockObject_MockObject + */ + private $arrayUtility; + /** * @return void */ @@ -159,6 +165,11 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->arrayUtility = $this->getMockBuilder(ArrayUtils::class) + ->setMethods(['flatten']) + ->disableOriginalConstructor() + ->getMock(); + $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectHelper->getObject( \Magento\Bundle\Model\Product\Type::class, @@ -175,6 +186,7 @@ protected function setUp() 'priceCurrency' => $this->priceCurrency, 'serializer' => $this->serializer, 'metadataPool' => $this->metadataPool, + 'arrayUtility' => $this->arrayUtility ] ); } @@ -421,6 +433,8 @@ function ($key) use ($optionCollection, $selectionCollection) { return $resultValue; } ); + $bundleOptions = [3 => 5]; + $product->expects($this->any()) ->method('getId') ->willReturn(333); @@ -438,9 +452,7 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -491,6 +503,9 @@ function ($key) use ($optionCollection, $selectionCollection) { $option->expects($this->once()) ->method('getTitle') ->willReturn('Title for option'); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); @@ -653,6 +668,8 @@ function ($key) use ($optionCollection, $selectionCollection) { return $resultValue; } ); + $bundleOptions = [3 => 5]; + $product->expects($this->any()) ->method('getId') ->willReturn(333); @@ -672,7 +689,10 @@ function ($key) use ($optionCollection, $selectionCollection) { ->method('setStoreFilter'); $buyRequest->expects($this->once()) ->method('getBundleOption') - ->willReturn([3 => 5]); + ->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -890,9 +910,10 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -943,6 +964,9 @@ function ($key) use ($optionCollection, $selectionCollection) { $option->expects($this->once()) ->method('getTitle') ->willReturn('Title for option'); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); @@ -1053,13 +1077,15 @@ function ($key) use ($optionCollection) { ->willReturn(333); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([]); + + $bundleOptions = []; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $result = $this->model->prepareForCartAdvanced($buyRequest, $product, 'single'); $this->assertEquals([$product], $result); } @@ -1165,9 +1191,12 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->at(0)) ->method('getItems') ->willReturn([$selection]); @@ -1289,9 +1318,12 @@ function ($key) use ($optionCollection, $selectionCollection) { ->willReturn($option); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); diff --git a/app/code/Magento/Bundle/etc/frontend/di.xml b/app/code/Magento/Bundle/etc/frontend/di.xml index 54f6d3b4b0f42..fc820ff87a129 100644 --- a/app/code/Magento/Bundle/etc/frontend/di.xml +++ b/app/code/Magento/Bundle/etc/frontend/di.xml @@ -13,4 +13,7 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="bundle" type="Magento\Bundle\Model\Plugin\Frontend\Product" sortOrder="100" /> + </type> </config> diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php index b0794f4564645..a8650a4e6e9e3 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php @@ -242,7 +242,7 @@ public function testSaveData($skus, $bunch, $allowImport) 'price_type' => 'fixed', 'shipment_type' => '1', 'default_qty' => '1', - 'is_defaul' => '1', + 'is_default' => '1', 'position' => '1', 'option_id' => '1'] ] @@ -264,7 +264,7 @@ public function testSaveData($skus, $bunch, $allowImport) 'price_type' => 'percent', 'shipment_type' => 0, 'default_qty' => '2', - 'is_defaul' => '1', + 'is_default' => '1', 'position' => '6', 'option_id' => '6'] ] @@ -324,7 +324,7 @@ public function saveDataProvider() . 'price_type=fixed,' . 'shipment_type=separately,' . 'default_qty=1,' - . 'is_defaul=1,' + . 'is_default=1,' . 'position=1,' . 'option_id=1 | name=Bundle2,' . 'type=dropdown,' @@ -333,7 +333,7 @@ public function saveDataProvider() . 'price=10,' . 'price_type=fixed,' . 'default_qty=1,' - . 'is_defaul=1,' + . 'is_default=1,' . 'position=2,' . 'option_id=2' ], diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php index 727e18280d76f..2152f842d4a29 100644 --- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php +++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php @@ -94,7 +94,7 @@ private function splitTags($tagsPattern) $formattedTags = explode('|', $tagsPattern); foreach ($formattedTags as $formattedTag) { if ($tagsBatchSize + strlen($formattedTag) > $this->requestSize - count($formattedTagsChunk) - 1) { - yield implode('|', array_unique($formattedTagsChunk)); + yield implode('|', $formattedTagsChunk); $formattedTagsChunk = []; $tagsBatchSize = 0; } @@ -103,7 +103,7 @@ private function splitTags($tagsPattern) $formattedTagsChunk[] = $formattedTag; } if (!empty($formattedTagsChunk)) { - yield implode('|', array_unique($formattedTagsChunk)); + yield implode('|', $formattedTagsChunk); } } diff --git a/app/code/Magento/Captcha/CustomerData/Captcha.php b/app/code/Magento/Captcha/CustomerData/Captcha.php new file mode 100644 index 0000000000000..a744daacdc673 --- /dev/null +++ b/app/code/Magento/Captcha/CustomerData/Captcha.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Captcha\CustomerData; + +use Magento\Customer\CustomerData\SectionSourceInterface; + +/** + * Captcha section + */ +class Captcha extends \Magento\Framework\DataObject implements SectionSourceInterface +{ + /** + * @var array + */ + private $formIds; + + /** + * @var \Magento\Captcha\Helper\Data + */ + private $helper; + + /** + * @param \Magento\Captcha\Helper\Data $helper + * @param array $formIds + * @param array $data + * @codeCoverageIgnore + */ + public function __construct( + \Magento\Captcha\Helper\Data $helper, + array $formIds, + array $data = [] + ) { + parent::__construct($data); + $this->helper = $helper; + $this->formIds = $formIds; + } + + /** + * @inheritdoc + */ + public function getSectionData() :array + { + $data = []; + + foreach ($this->formIds as $formId) { + $captchaModel = $this->helper->getCaptcha($formId); + $data[$formId] = [ + 'isRequired' => $captchaModel->isRequired(), + 'timestamp' => time() + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php index ef5f5a8edce71..34ee62044ff57 100644 --- a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php +++ b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php @@ -5,6 +5,9 @@ */ namespace Magento\Captcha\Model\Checkout; +/** + * Configuration provider for Captcha rendering. + */ class ConfigProvider implements \Magento\Checkout\Model\ConfigProviderInterface { /** @@ -38,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig() { @@ -49,7 +52,8 @@ public function getConfig() 'imageHeight' => $this->getImageHeight($formId), 'imageSrc' => $this->getImageSrc($formId), 'refreshUrl' => $this->getRefreshUrl(), - 'isRequired' => $this->isRequired($formId) + 'isRequired' => $this->isRequired($formId), + 'timestamp' => time() ]; } return $config; diff --git a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php index f04e9ac8611bc..84ac71046c343 100644 --- a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php +++ b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php @@ -3,18 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types=1); namespace Magento\Captcha\Model\Customer\Plugin; use Magento\Captcha\Helper\Data as CaptchaHelper; -use Magento\Customer\Controller\Ajax\Login; -use Magento\Framework\Controller\Result\Json; -use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Session\SessionManagerInterface; +use Magento\Framework\Controller\Result\JsonFactory; /** - * The plugin for ajax login controller. + * Around plugin for login action. */ class AjaxLogin { @@ -67,14 +64,16 @@ public function __construct( } /** - * Validates captcha during request execution. + * Check captcha data on login action. * - * @param Login $subject + * @param \Magento\Customer\Controller\Ajax\Login $subject * @param \Closure $proceed * @return $this + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function aroundExecute( - Login $subject, + \Magento\Customer\Controller\Ajax\Login $subject, \Closure $proceed ) { $captchaFormIdField = 'captcha_form_id'; @@ -99,15 +98,13 @@ public function aroundExecute( foreach ($this->formIds as $formId) { if ($formId === $loginFormId) { $captchaModel = $this->helper->getCaptcha($formId); - if ($captchaModel->isRequired($username)) { if (!$captchaModel->isCorrect($captchaString)) { $this->sessionManager->setUsername($username); $captchaModel->logAttempt($username); - return $this->returnJsonError(__('Incorrect CAPTCHA'), true); + return $this->returnJsonError(__('Incorrect CAPTCHA')); } } - $captchaModel->logAttempt($username); } } @@ -115,15 +112,14 @@ public function aroundExecute( } /** - * Gets Json response. + * Format JSON response. * * @param \Magento\Framework\Phrase $phrase - * @param bool $isCaptchaRequired - * @return Json + * @return \Magento\Framework\Controller\Result\Json */ - private function returnJsonError(\Magento\Framework\Phrase $phrase, bool $isCaptchaRequired = false): Json + private function returnJsonError(\Magento\Framework\Phrase $phrase): \Magento\Framework\Controller\Result\Json { $resultJson = $this->resultJsonFactory->create(); - return $resultJson->setData(['errors' => true, 'message' => $phrase, 'captcha' => $isCaptchaRequired]); + return $resultJson->setData(['errors' => true, 'message' => $phrase]); } } diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php index cf6690df5c85d..483f9c3fb4d20 100644 --- a/app/code/Magento/Captcha/Model/DefaultModel.php +++ b/app/code/Magento/Captcha/Model/DefaultModel.php @@ -78,6 +78,11 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model */ protected $session; + /** + * @var string + */ + private $words; + /** * @param \Magento\Framework\Session\SessionManagerInterface $session * @param \Magento\Captcha\Helper\Data $captchaData @@ -311,18 +316,18 @@ public function getImgUrl() */ public function isCorrect($word) { - $storedWord = $this->getWord(); + $storedWords = $this->getWords(); $this->clearWord(); - if (!$word || !$storedWord) { + if (!$word || !$storedWords) { return false; } if (!$this->isCaseSensitive()) { - $storedWord = strtolower($storedWord); + $storedWords = strtolower($storedWords); $word = strtolower($word); } - return $word === $storedWord; + return in_array($word, explode(',', $storedWords)); } /** @@ -481,7 +486,7 @@ private function getTargetForms() /** * Get captcha word * - * @return string + * @return string|null */ public function getWord() { @@ -489,6 +494,17 @@ public function getWord() return time() < $sessionData['expires'] ? $sessionData['data'] : null; } + /** + * Get captcha words + * + * @return string|null + */ + private function getWords() + { + $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD)); + return time() < $sessionData['expires'] ? $sessionData['words'] : null; + } + /** * Set captcha word * @@ -498,9 +514,10 @@ public function getWord() */ protected function setWord($word) { + $this->words = $this->words ? $this->words . ',' . $word : $word; $this->session->setData( $this->getFormIdKey(self::SESSION_WORD), - ['data' => $word, 'expires' => time() + $this->getTimeout()] + ['data' => $word, 'words' => $this->words, 'expires' => time() + $this->getTimeout()] ); $this->word = $word; return $this; diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml new file mode 100644 index 0000000000000..7a0557c4a2744 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSignInPopupFormSection"> + <element name="captchaField" type="input" selector="#captcha_user_login"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml index 8f764899706ac..a088266f760a5 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -64,6 +64,52 @@ <!--Roll back configuration--> <scrollToTopOfPage stepKey="ScrollToTop"/> <click selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="ClickToCloseCaptcha"/> - + </test> + <test name="CaptchaWithDisabledGuestCheckout"> + <annotations> + <features value="Captcha"/> + <stories value="MC-5602 - CAPTCHA doesn't appear in login popup after refreshing page."/> + <title value="Captcha is displaying on login form with disabled guest checkout"/> + <description value="Captcha is displaying on login form with disabled guest checkout"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96691"/> + <group value="captcha"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/options/guest_checkout 0" stepKey="disableGuestCheckout"/> + <magentoCLI command="config:set customer/captcha/failed_attempts_login 1" stepKey="decreaseLoginAttempt"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <magentoCLI command="config:set checkout/options/guest_checkout 1" stepKey="enableGuestCheckout"/> + <magentoCLI command="config:set customer/captcha/failed_attempts_login 3" stepKey="increaseLoginAttempt"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct1"/> + </after> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.sku$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart" /> + <waitForText userInput="You added $$createSimpleProduct.name$$ to your shopping cart." stepKey="waitForText"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitEmailFieldVisible"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.password}}" userInput="incorrectPassword" stepKey="fillIncorrectCustomerPassword"/> + <click selector="{{StorefrontCustomerSignInPopupFormSection.signIn}}" stepKey="clickSignIn"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.errorMessage}}" stepKey="seeErrorMessage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart2"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitEmailFieldVisible2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaField}}" stepKey="seeCaptchaField2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaImg}}" stepKey="seeCaptchaImage2"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton2"/> </test> </tests> diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php index 655fcd6118e25..8764dbd4cec11 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php @@ -77,6 +77,7 @@ public function testGetConfig($isRequired, $captchaGenerations, $expectedConfig) ->will($this->returnValue('https://magento.com/captcha')); $config = $this->model->getConfig(); + unset($config['captcha'][$this->formId]['timestamp']); $this->assertEquals($config, $expectedConfig); } diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php index 0764ea36897d1..ec2a49f3fc566 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php @@ -158,7 +158,7 @@ public function testAroundExecuteIncorrectCaptcha() $this->resultJsonMock ->expects($this->once()) ->method('setData') - ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA'), 'captcha' => true]) + ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA')]) ->will($this->returnSelf()); $closure = function () { diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php index 1edbcc029e4ce..eef75d2c01ec7 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php @@ -183,7 +183,13 @@ public function testIsCorrect() { self::$_defaultConfig['case_sensitive'] = '1'; $this->assertFalse($this->_object->isCorrect('abcdef5')); - $sessionData = ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]]; + $sessionData = [ + 'user_create_word' => [ + 'data' => 'AbCdEf5', + 'words' => 'AbCdEf5', + 'expires' => time() + self::EXPIRE_FRAME + ] + ]; $this->_object->getSession()->setData($sessionData); self::$_defaultConfig['case_sensitive'] = '0'; $this->assertTrue($this->_object->isCorrect('abcdef5')); @@ -224,7 +230,7 @@ public function testGetWord() { $this->assertEquals($this->_object->getWord(), 'AbCdEf5'); $this->_object->getSession()->setData( - ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() - 360]] + ['user_create_word' => ['data' => 'AbCdEf5', 'words' => 'AbCdEf5','expires' => time() - 360]] ); $this->assertNull($this->_object->getWord()); } @@ -247,7 +253,13 @@ protected function _getSessionStub() ->getMock(); $session->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false)); - $session->setData(['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]]); + $session->setData([ + 'user_create_word' => [ + 'data' => 'AbCdEf5', + 'words' => 'AbCdEf5', + 'expires' => time() + self::EXPIRE_FRAME + ] + ]); return $session; } diff --git a/app/code/Magento/Captcha/etc/db_schema.xml b/app/code/Magento/Captcha/etc/db_schema.xml index b8987363569ca..158e2f43b9f5d 100644 --- a/app/code/Magento/Captcha/etc/db_schema.xml +++ b/app/code/Magento/Captcha/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="captcha_log" resource="default" engine="innodb" comment="Count Login Attempts"> <column xsi:type="varchar" name="type" nullable="false" length="32" comment="Type"/> - <column xsi:type="varchar" name="value" nullable="false" length="32" comment="Value"/> + <column xsi:type="varchar" name="value" nullable="false" length="255" comment="Value"/> <column xsi:type="int" name="count" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Count"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true" comment="Update Time"/> diff --git a/app/code/Magento/Captcha/etc/frontend/di.xml b/app/code/Magento/Captcha/etc/frontend/di.xml index 0c4ab0cda0735..490f1eab85196 100644 --- a/app/code/Magento/Captcha/etc/frontend/di.xml +++ b/app/code/Magento/Captcha/etc/frontend/di.xml @@ -20,4 +20,18 @@ </argument> </arguments> </type> + <type name="Magento\Captcha\CustomerData\Captcha"> + <arguments> + <argument name="formIds" xsi:type="array"> + <item name="user_login" xsi:type="string">user_login</item> + </argument> + </arguments> + </type> + <type name="Magento\Customer\CustomerData\SectionPoolInterface"> + <arguments> + <argument name="sectionSourceMap" xsi:type="array"> + <item name="captcha" xsi:type="string">Magento\Captcha\CustomerData\Captcha</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Captcha/etc/frontend/sections.xml b/app/code/Magento/Captcha/etc/frontend/sections.xml new file mode 100644 index 0000000000000..7f2070e10c8a9 --- /dev/null +++ b/app/code/Magento/Captcha/etc/frontend/sections.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd"> + <action name="customer/ajax/login"> + <section name="captcha"/> + </action> +</config> diff --git a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js index 52968e507e6bf..e79cfb35ee08d 100644 --- a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js +++ b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js @@ -22,6 +22,7 @@ define([ imageHeight: captchaData.imageHeight, refreshUrl: captchaData.refreshUrl, isLoading: ko.observable(false), + timestamp: null, /** * @return {String} diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js index f78b848312702..d79c42a711565 100644 --- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js +++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js @@ -7,8 +7,10 @@ define([ 'jquery', 'uiComponent', 'Magento_Captcha/js/model/captcha', - 'Magento_Captcha/js/model/captchaList' -], function ($, Component, Captcha, captchaList) { + 'Magento_Captcha/js/model/captchaList', + 'Magento_Customer/js/customer-data', + 'underscore' +], function ($, Component, Captcha, captchaList, customerData, _) { 'use strict'; var captchaConfig; @@ -34,12 +36,49 @@ define([ if (window[this.configSource] && window[this.configSource].captcha) { captchaConfig = window[this.configSource].captcha; $.each(captchaConfig, function (formId, captchaData) { + var captcha; + captchaData.formId = formId; - captchaList.add(Captcha(captchaData)); - }); + captcha = Captcha(captchaData); + this.checkCustomerData(formId, customerData.get('captcha')(), captcha); + this.subscribeCustomerData(formId, captcha); + captchaList.add(captcha); + }.bind(this)); + } + }, + + /** + * Check customer data for captcha configuration. + * + * @param {String} formId + * @param {Object} captchaData + * @param {Object} captcha + */ + checkCustomerData: function (formId, captchaData, captcha) { + if (!_.isEmpty(captchaData) && + !_.isEmpty(captchaData)[formId] && + captchaData[formId].timestamp > captcha.timestamp + ) { + if (!captcha.isRequired() && captchaData[formId].isRequired) { + captcha.refresh(); + } + captcha.isRequired(captchaData[formId].isRequired); + captcha.timestamp = captchaData[formId].timestamp; } }, + /** + * Subscribe for customer data updates. + * + * @param {String} formId + * @param {Object} captcha + */ + subscribeCustomerData: function (formId, captcha) { + customerData.get('captcha').subscribe(function (captchaData) { + this.checkCustomerData(formId, captchaData, captcha); + }.bind(this)); + }, + /** * @return {Boolean} */ @@ -90,6 +129,8 @@ define([ }, /** + * Set isRequired on current captcha model. + * * @param {Boolean} flag */ setIsRequired: function (flag) { diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js index a8efd0865bbb8..49528f6ce8501 100644 --- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js +++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js @@ -4,44 +4,36 @@ */ define([ - 'underscore', - 'Magento_Captcha/js/view/checkout/defaultCaptcha', - 'Magento_Captcha/js/model/captchaList', - 'Magento_Customer/js/action/login' - ], - function (_, defaultCaptcha, captchaList, loginAction) { - 'use strict'; - - return defaultCaptcha.extend({ - /** @inheritdoc */ - initialize: function () { - var self = this, - currentCaptcha; - - this._super(); - currentCaptcha = captchaList.getCaptchaByFormId(this.formId); - - if (currentCaptcha != null) { - currentCaptcha.setIsVisible(true); - this.setCurrentCaptcha(currentCaptcha); - - loginAction.registerLoginCallback(function (loginData, response) { - if (!loginData['captcha_form_id'] || loginData['captcha_form_id'] !== self.formId) { - return; - } - - if (_.isUndefined(response) || !response.errors) { - return; - } - - // check if captcha should be required after login attempt - if (!self.isRequired() && response.captcha && self.isRequired() !== response.captcha) { - self.setIsRequired(response.captcha); - } - - self.refresh(); - }); - } + 'Magento_Captcha/js/view/checkout/defaultCaptcha', + 'Magento_Captcha/js/model/captchaList', + 'Magento_Customer/js/action/login', + 'underscore' +], +function (defaultCaptcha, captchaList, loginAction, _) { + 'use strict'; + + return defaultCaptcha.extend({ + /** @inheritdoc */ + initialize: function () { + var self = this, + currentCaptcha; + + this._super(); + currentCaptcha = captchaList.getCaptchaByFormId(this.formId); + + if (currentCaptcha != null) { + currentCaptcha.setIsVisible(true); + this.setCurrentCaptcha(currentCaptcha); + + loginAction.registerLoginCallback(function (loginData) { + if (loginData['captcha_form_id'] && + loginData['captcha_form_id'] === self.formId && + self.isRequired() + ) { + _.defer(self.refresh.bind(self)); + } + }); } - }); + } }); +}); diff --git a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html index 8923c81bf4bb3..3f48ec330c0a4 100644 --- a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html +++ b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ --> -<!-- ko if: (getIsVisible())--> <input name="captcha_form_id" type="hidden" data-bind="value: formId, attr: {'data-scope': dataScope}" /> -<!-- /ko --> <!-- ko if: (isRequired() && getIsVisible())--> <div class="field captcha required" data-bind="blockLoader: getIsLoading()"> <label data-bind="attr: {for: 'captcha_' + formId}" class="label"><span data-bind="i18n: 'Please type the letters and numbers below'"></span></label> <div class="control captcha"> - <input name="captcha_string" type="text" class="input-text required-entry" data-bind="value: captchaValue(), attr: {'data-scope': dataScope}" autocomplete="off"/> + <input name="captcha_string" type="text" class="input-text required-entry" data-bind="value: captchaValue(), attr: {id: 'captcha_' + formId, 'data-scope': dataScope}" autocomplete="off"/> <div class="nested"> <div class="field captcha no-label"> <div class="control captcha-image"> diff --git a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php index 30ac06107ba1d..34656d522a72c 100644 --- a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php +++ b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php @@ -32,18 +32,20 @@ public function save(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $pro * * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\StateException + * @throws \Magento\Framework\Exception\InputException */ public function delete(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $productLink); /** * Remove the product assignment from the category by category id and sku * - * @param string $sku + * @param string $categoryId * @param string $sku * @return bool will returned True if products successfully deleted * * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\StateException + * @throws \Magento\Framework\Exception\InputException */ public function deleteByIds($categoryId, $sku); } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php index b77a5e2e95241..3266922d116ec 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php @@ -11,6 +11,9 @@ */ namespace Magento\Catalog\Block\Adminhtml\Category\Helper; +/** + * Pricestep Helper + */ class Pricestep extends \Magento\Framework\Data\Form\Element\Text { /** @@ -40,7 +43,7 @@ public function getElementHtml() $disabled = true; } - parent::addClass('validate-number validate-number-range number-range-0.01-1000000000'); + parent::addClass('validate-number validate-number-range number-range-0.01-9999999999999999'); $html = parent::getElementHtml(); $htmlId = 'use_config_' . $this->getHtmlId(); $html .= '<br/><input id="' . $htmlId . '" name="use_config[]" value="' . $this->getId() . '"'; diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php index 1b188de40710f..3b9036c1fbbc0 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php @@ -6,13 +6,13 @@ namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Set; /** - * Adminhtml Catalog Attribute Set Main Block - * * @author Magento Core Team <core@magentocommerce.com> */ use Magento\Catalog\Model\Entity\Product\Attribute\Group\AttributeMapperInterface; /** + * Adminhtml Catalog Attribute Set Main Block. + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 @@ -140,7 +140,7 @@ protected function _prepareLayout() ) . '\', \'' . $this->getUrl( 'catalog/*/delete', ['id' => $setId] - ) . '\')', + ) . '\',{data: {}})', 'class' => 'delete' ] ); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php index e4f700e5790a2..e5ce59c550af1 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php @@ -10,9 +10,13 @@ use Magento\Catalog\Model\Product; /** + * Crossel product edit tab + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see \Magento\Catalog\Ui\DataProvider\Product\Related\CrossSellDataProvider */ class Crosssell extends Extended { diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php index 0c59a7402dac1..23b927598e8e7 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php @@ -9,8 +9,12 @@ use Magento\Backend\Block\Widget\Grid\Extended; /** + * Related product edit tab + * * @api * @since 100.0.2 + * @deprecated Not used since related products grid moved to UI components. + * @see \Magento\Catalog\Ui\DataProvider\Product\Related\RelatedDataProvider */ class Related extends Extended { @@ -318,7 +322,7 @@ protected function _prepareColumns() } /** - * Rerieve grid URL + * Retrieve grid URL * * @return string */ diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php index 323b1785bc96e..41ad72ca39e53 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php @@ -6,8 +6,12 @@ namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab; /** + * Upsell product edit tab + * * @api * @since 100.0.2 + * @deprecated Not used since upsell products grid moved to UI components. + * @see \Magento\Catalog\Ui\DataProvider\Product\Related\CrossSellDataProvider */ class Upsell extends \Magento\Backend\Block\Widget\Grid\Extended { diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php index 5089b37f90c58..a5be6223bee75 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php @@ -8,6 +8,9 @@ use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +/** + * Controller for category listing + */ class Index extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index 49e601357c605..39ed11b1806cd 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -197,12 +197,12 @@ public function execute() $attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]); if (strlen($attributeCode) > 0) { $validatorAttrCode = new \Zend_Validate_Regex( - ['pattern' => '/^[a-z\x{600}-\x{6FF}][a-z\x{600}-\x{6FF}_0-9]{0,30}$/u'] + ['pattern' => '/^[a-zA-Z\x{600}-\x{6FF}][a-zA-Z\x{600}-\x{6FF}_0-9]{0,30}$/u'] ); if (!$validatorAttrCode->isValid($attributeCode)) { $this->messageManager->addErrorMessage( __( - 'Attribute code "%1" is invalid. Please use only letters (a-z), ' . + 'Attribute code "%1" is invalid. Please use only letters (a-z or A-Z), ' . 'numbers (0-9) or underscore(_) in this field, first character should be a letter.', $attributeCode ) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php index fa94a2fb7be2c..e51d3ffe94ae2 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php @@ -6,7 +6,17 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class Crosssell extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Crosssell + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class Crosssell extends Product implements HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\LayoutFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php index daaf2c21ee2b9..5039d0c052b5d 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php @@ -6,7 +6,17 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class CrosssellGrid extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class CrosssellGrid + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class CrosssellGrid extends Product implements HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\LayoutFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php index 4bc15d14a29b1..f54f8d469c3e8 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php @@ -7,7 +7,17 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class Related extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Related + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class Related extends Product implements HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\LayoutFactory @@ -29,6 +39,8 @@ public function __construct( } /** + * Execute + * * @return \Magento\Framework\View\Result\Layout */ public function execute() diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php index b2fc7ebe1eb31..b1092bba0d369 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php @@ -7,6 +7,15 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class RelatedGrid extends Related +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class RelatedGrid + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class RelatedGrid extends Related implements HttpPostActionInterface { } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php index 614bddd0ebc87..1cec8e8678797 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php @@ -6,7 +6,17 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class Upsell extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Upsell + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ +class Upsell extends Product implements HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\LayoutFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php index 50beb588b15df..581531e7c93fb 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php @@ -6,7 +6,17 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class UpsellGrid extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class UpsellGrid + * + * @package Magento\Catalog\Controller\Adminhtml\Product + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ +class UpsellGrid extends Product implements HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\LayoutFactory diff --git a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php index 7cc3eb9e3d2da..25f6d0c323687 100644 --- a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php +++ b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php @@ -12,6 +12,8 @@ use Magento\Store\Model\Store; /** + * Cron job for removing outdated prices. + * * Cron operation is responsible for deleting all product prices on WEBSITE level * in case 'Catalog Price Scope' configuration parameter is set to GLOBAL. */ @@ -76,7 +78,7 @@ public function execute() /** * Checks if price scope config option explicitly equal to global value. * - * Such strict comparision is required to prevent price deleting when + * Such strict comparison is required to prevent price deleting when * price scope config option is null for some reason. * * @return bool diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index 29fb0d282a87c..1f62bed93e23f 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -1152,13 +1152,14 @@ public function getIdentities() $identities = [ self::CACHE_TAG . '_' . $this->getId(), ]; - if (!$this->getId() || $this->hasDataChanges() - || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU) - ) { + if ($this->hasDataChanges()) { + $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId(); + } + if (!$this->getId() || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU)) { $identities[] = self::CACHE_TAG; $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId(); } - return $identities; + return array_unique($identities); } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index fbe0d4b550fa6..2252b3e3d5506 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -10,7 +10,8 @@ use Magento\Framework\EntityManager\MetadataPool; /** - * Class FlatTableBuilder + * Class for building flat index + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FlatTableBuilder @@ -346,12 +347,21 @@ protected function _updateTemporaryTableByStoreValues( } //Update not simple attributes (eg. dropdown) - if (isset($flatColumns[$attributeCode . $valueFieldSuffix])) { - $select = $this->_connection->select()->joinInner( - ['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], - 't.option_id = et.' . $attributeCode . ' AND t.store_id=' . $storeId, - [$attributeCode . $valueFieldSuffix => 't.value'] - ); + $columnName = $attributeCode . $valueFieldSuffix; + if (isset($flatColumns[$columnName])) { + $columnValue = $this->_connection->getIfNullSql('ts.value', 't0.value'); + $select = $this->_connection->select(); + $select->joinLeft( + ['t0' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], + 't0.option_id = et.' . $attributeCode . ' AND t0.store_id = 0', + [] + )->joinLeft( + ['ts' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], + 'ts.option_id = et.' . $attributeCode . ' AND ts.store_id = ' . $storeId, + [] + )->columns( + [$columnName => $columnValue] + )->where($columnValue . ' IS NOT NULL'); if (!empty($changedIds)) { $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); } @@ -374,6 +384,8 @@ protected function _getTemporaryTableName($tableName) } /** + * Get metadata pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php index d21a8666ec0ac..f2e2e67f944e9 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php @@ -139,7 +139,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) } /** - * Get fiter items count + * Get filter items count * * @return int */ diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php index 4d2878b0b1e84..07c9c2eaa2491 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ +namespace Magento\Catalog\Model\Layer\Filter\Item; + /** * Item Data Builder */ -namespace Magento\Catalog\Model\Layer\Filter\Item; - class DataBuilder { /** @@ -29,7 +29,7 @@ class DataBuilder * Add Item Data * * @param string $label - * @param string $label + * @param string $value * @param int $count * @return void */ diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 44c5c891f4d6a..5d47e85768fad 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -727,7 +727,7 @@ public function getIdBySku($sku) public function getCategoryId() { $category = $this->_registry->registry('current_category'); - if ($category) { + if ($category && in_array($category->getId(), $this->getCategoryIds())) { return $category->getId(); } return false; diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php index c2108b0273bdb..be1e523960f27 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Product\Attribute\Backend; use Magento\Catalog\Model\Product\Attribute\Source\Boolean as BooleanSource; @@ -25,7 +27,9 @@ public function beforeSave($object) $attributeCode = $this->getAttribute()->getName(); if ($object->getData('use_config_' . $attributeCode)) { $object->setData($attributeCode, BooleanSource::VALUE_USE_CONFIG); + return $this; } - return $this; + + return parent::beforeSave($object); } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/AbstractHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/AbstractHandler.php new file mode 100644 index 0000000000000..fc0f090937db9 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/AbstractHandler.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\Attribute\Backend\TierPrice; + +use Magento\Framework\EntityManager\Operation\ExtensionInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Customer\Api\GroupManagementInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice; + +/** + * Tier price data abstract handler. + */ +abstract class AbstractHandler implements ExtensionInterface +{ + /** + * @var \Magento\Customer\Api\GroupManagementInterface + */ + protected $groupManagement; + + /** + * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement + */ + public function __construct( + GroupManagementInterface $groupManagement + ) { + $this->groupManagement = $groupManagement; + } + + /** + * Get additional tier price fields. + * + * @param array $objectArray + * @return array + */ + protected function getAdditionalFields(array $objectArray): array + { + $percentageValue = $this->getPercentage($objectArray); + + return [ + 'value' => $percentageValue ? null : $objectArray['price'], + 'percentage_value' => $percentageValue ?: null, + ]; + } + + /** + * Check whether price has percentage value. + * + * @param array $priceRow + * @return float|null + */ + protected function getPercentage(array $priceRow): ?float + { + return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value']) + ? (float)$priceRow['percentage_value'] + : null; + } + + /** + * Prepare tier price data by provided price row data. + * + * @param array $data + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function prepareTierPrice(array $data): array + { + $useForAllGroups = (int)$data['cust_group'] === $this->groupManagement->getAllCustomersGroup()->getId(); + $customerGroupId = $useForAllGroups ? 0 : $data['cust_group']; + $tierPrice = array_merge( + $this->getAdditionalFields($data), + [ + 'website_id' => $data['website_id'], + 'all_groups' => (int)$useForAllGroups, + 'customer_group_id' => $customerGroupId, + 'value' => $data['price'] ?? null, + 'qty' => $this->parseQty($data['price_qty']), + ] + ); + + return $tierPrice; + } + + /** + * Parse quantity value into float. + * + * @param mixed $value + * @return float|int + */ + protected function parseQty($value) + { + return $value * 1; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php index 248d8ed221250..9cb2ac0145898 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php @@ -18,7 +18,7 @@ /** * Process tier price data for handled new product */ -class SaveHandler implements ExtensionInterface +class SaveHandler extends AbstractHandler { /** * @var \Magento\Store\Model\StoreManagerInterface @@ -30,11 +30,6 @@ class SaveHandler implements ExtensionInterface */ private $attributeRepository; - /** - * @var \Magento\Customer\Api\GroupManagementInterface - */ - private $groupManagement; - /** * @var \Magento\Framework\EntityManager\MetadataPool */ @@ -59,9 +54,10 @@ public function __construct( MetadataPool $metadataPool, Tierprice $tierPriceResource ) { + parent::__construct($groupManagement); + $this->storeManager = $storeManager; $this->attributeRepository = $attributeRepository; - $this->groupManagement = $groupManagement; $this->metadataPoll = $metadataPool; $this->tierPriceResource = $tierPriceResource; } @@ -72,8 +68,6 @@ public function __construct( * @param \Magento\Catalog\Api\Data\ProductInterface|object $entity * @param array $arguments * @return \Magento\Catalog\Api\Data\ProductInterface|object - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\InputException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -115,57 +109,4 @@ public function execute($entity, $arguments = []) return $entity; } - - /** - * Get additional tier price fields - * - * @param array $objectArray - * @return array - */ - private function getAdditionalFields(array $objectArray): array - { - $percentageValue = $this->getPercentage($objectArray); - return [ - 'value' => $percentageValue ? null : $objectArray['price'], - 'percentage_value' => $percentageValue ?: null, - ]; - } - - /** - * Check whether price has percentage value. - * - * @param array $priceRow - * @return int|null - */ - private function getPercentage(array $priceRow): ?int - { - return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value']) - ? (int)$priceRow['percentage_value'] - : null; - } - - /** - * Prepare tier price data by provided price row data - * - * @param array $data - * @return array - * @throws \Magento\Framework\Exception\LocalizedException - */ - private function prepareTierPrice(array $data): array - { - $useForAllGroups = (int)$data['cust_group'] === $this->groupManagement->getAllCustomersGroup()->getId(); - $customerGroupId = $useForAllGroups ? 0 : $data['cust_group']; - $tierPrice = array_merge( - $this->getAdditionalFields($data), - [ - 'website_id' => $data['website_id'], - 'all_groups' => (int)$useForAllGroups, - 'customer_group_id' => $customerGroupId, - 'value' => $data['price'] ?? null, - 'qty' => (int)$data['price_qty'] - ] - ); - - return $tierPrice; - } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php index aef3e87586015..663b7facf4257 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php @@ -16,9 +16,9 @@ use Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice; /** - * Process tier price data for handled existing product + * Process tier price data for handled existing product. */ -class UpdateHandler implements ExtensionInterface +class UpdateHandler extends AbstractHandler { /** * @var \Magento\Store\Model\StoreManagerInterface @@ -30,11 +30,6 @@ class UpdateHandler implements ExtensionInterface */ private $attributeRepository; - /** - * @var \Magento\Customer\Api\GroupManagementInterface - */ - private $groupManagement; - /** * @var \Magento\Framework\EntityManager\MetadataPool */ @@ -59,9 +54,10 @@ public function __construct( MetadataPool $metadataPool, Tierprice $tierPriceResource ) { + parent::__construct($groupManagement); + $this->storeManager = $storeManager; $this->attributeRepository = $attributeRepository; - $this->groupManagement = $groupManagement; $this->metadataPoll = $metadataPool; $this->tierPriceResource = $tierPriceResource; } @@ -72,8 +68,7 @@ public function __construct( * @param \Magento\Catalog\Api\Data\ProductInterface|object $entity * @param array $arguments * @return \Magento\Catalog\Api\Data\ProductInterface|object - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\InputException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entity, $arguments = []) @@ -119,34 +114,6 @@ public function execute($entity, $arguments = []) return $entity; } - /** - * Get additional tier price fields - * - * @param array $objectArray - * @return array - */ - private function getAdditionalFields(array $objectArray): array - { - $percentageValue = $this->getPercentage($objectArray); - return [ - 'value' => $percentageValue ? null : $objectArray['price'], - 'percentage_value' => $percentageValue ?: null, - ]; - } - - /** - * Check whether price has percentage value. - * - * @param array $priceRow - * @return int|null - */ - private function getPercentage(array $priceRow): ?int - { - return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value']) - ? (int)$priceRow['percentage_value'] - : null; - } - /** * Update existing tier prices for processed product * @@ -226,39 +193,15 @@ private function deleteValues(int $productId, array $valuesToDelete): bool */ private function getPriceKey(array $priceData): string { + $qty = $this->parseQty($priceData['price_qty']); $key = implode( '-', - array_merge([$priceData['website_id'], $priceData['cust_group']], [(int)$priceData['price_qty']]) + array_merge([$priceData['website_id'], $priceData['cust_group']], [$qty]) ); return $key; } - /** - * Prepare tier price data by provided price row data - * - * @param array $data - * @return array - * @throws \Magento\Framework\Exception\LocalizedException - */ - private function prepareTierPrice(array $data): array - { - $useForAllGroups = (int)$data['cust_group'] === $this->groupManagement->getAllCustomersGroup()->getId(); - $customerGroupId = $useForAllGroups ? 0 : $data['cust_group']; - $tierPrice = array_merge( - $this->getAdditionalFields($data), - [ - 'website_id' => $data['website_id'], - 'all_groups' => (int)$useForAllGroups, - 'customer_group_id' => $customerGroupId, - 'value' => $data['price'] ?? null, - 'qty' => (int)$data['price_qty'] - ] - ); - - return $tierPrice; - } - /** * Check by id is website global * diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php index 99d5016f5cdb9..08455430ccac8 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php @@ -28,13 +28,20 @@ class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator */ protected $priceTypes; + /** + * @var \Magento\Framework\Locale\FormatInterface + */ + private $localeFormat; + /** * @param \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig * @param \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig + * @param \Magento\Framework\Locale\FormatInterface|null $localeFormat */ public function __construct( \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig, - \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig + \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig, + \Magento\Framework\Locale\FormatInterface $localeFormat = null ) { foreach ($productOptionConfig->getAll() as $option) { foreach ($option['types'] as $type) { @@ -45,6 +52,9 @@ public function __construct( foreach ($priceConfig->toOptionArray() as $item) { $this->priceTypes[] = $item['value']; } + + $this->localeFormat = $localeFormat ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Locale\FormatInterface::class); } /** @@ -137,11 +147,11 @@ protected function validateOptionType(Option $option) */ protected function validateOptionValue(Option $option) { - return $this->isInRange($option->getPriceType(), $this->priceTypes); + return $this->isInRange($option->getPriceType(), $this->priceTypes) && $this->isNumber($option->getPrice()); } /** - * Check whether value is empty + * Check whether the value is empty * * @param mixed $value * @return bool @@ -152,7 +162,7 @@ protected function isEmpty($value) } /** - * Check whether value is in range + * Check whether the value is in range * * @param string $value * @param array $range @@ -164,13 +174,24 @@ protected function isInRange($value, array $range) } /** - * Check whether value is not negative + * Check whether the value is negative * * @param string $value * @return bool */ protected function isNegative($value) { - return (int) $value < 0; + return $this->localeFormat->getNumber($value) < 0; + } + + /** + * Check whether the value is a number + * + * @param string $value + * @return bool + */ + public function isNumber($value) + { + return is_numeric($this->localeFormat->getNumber($value)); } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php index 44756890b6ed7..209531f599811 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php @@ -8,6 +8,9 @@ use Magento\Catalog\Model\Product\Option; +/** + * Select validator class + */ class Select extends DefaultValidator { /** @@ -83,7 +86,7 @@ protected function isValidOptionPrice($priceType, $price, $storeId) if (!$priceType && !$price) { return true; } - if (!$this->isInRange($priceType, $this->priceTypes)) { + if (!$this->isInRange($priceType, $this->priceTypes) || !$this->isNumber($price)) { return false; } diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 1bddd2d07cd81..3ee064670a460 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -97,7 +97,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function get(array $skus) { @@ -107,7 +107,7 @@ public function get(array $skus) } /** - * {@inheritdoc} + * @inheritdoc */ public function update(array $prices) { @@ -128,7 +128,7 @@ public function update(array $prices) } /** - * {@inheritdoc} + * @inheritdoc */ public function replace(array $prices) { @@ -144,7 +144,7 @@ public function replace(array $prices) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(array $prices) { @@ -171,16 +171,17 @@ private function getExistingPrices(array $skus, $groupBySku = false) $ids = $this->retrieveAffectedIds($skus); $rawPrices = $this->tierPricePersistence->get($ids); $prices = []; - - $linkField = $this->tierPricePersistence->getEntityLinkField(); - $skuByIdLookup = $this->buildSkuByIdLookup($skus); - foreach ($rawPrices as $rawPrice) { - $sku = $skuByIdLookup[$rawPrice[$linkField]]; - $price = $this->tierPriceFactory->create($rawPrice, $sku); - if ($groupBySku) { - $prices[$sku][] = $price; - } else { - $prices[] = $price; + if ($rawPrices) { + $linkField = $this->tierPricePersistence->getEntityLinkField(); + $skuByIdLookup = $this->buildSkuByIdLookup($skus); + foreach ($rawPrices as $rawPrice) { + $sku = $skuByIdLookup[$rawPrice[$linkField]]; + $price = $this->tierPriceFactory->create($rawPrice, $sku); + if ($groupBySku) { + $prices[$sku][] = $price; + } else { + $prices[] = $price; + } } } diff --git a/app/code/Magento/Catalog/Model/ProductIdLocator.php b/app/code/Magento/Catalog/Model/ProductIdLocator.php index 2d9af6829ad6e..2d382164f2649 100644 --- a/app/code/Magento/Catalog/Model/ProductIdLocator.php +++ b/app/code/Magento/Catalog/Model/ProductIdLocator.php @@ -37,23 +37,43 @@ class ProductIdLocator implements \Magento\Catalog\Model\ProductIdLocatorInterfa */ private $idsBySku = []; + /** + * Batch size to iterate collection + * + * @var int + */ + private $batchSize; + /** * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory - * @param string $limitIdsBySkuValues + * @param string $idsLimit + * @param int $batchSize defines how many items can be processed by one iteration */ public function __construct( \Magento\Framework\EntityManager\MetadataPool $metadataPool, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory, - $idsLimit + $idsLimit, + int $batchSize = 5000 ) { $this->metadataPool = $metadataPool; $this->collectionFactory = $collectionFactory; $this->idsLimit = (int)$idsLimit; + $this->batchSize = $batchSize; } /** - * {@inheritdoc} + * @inheritdoc + * + * Load product items by provided products SKUs. + * Products collection will be iterated by pages with the $this->batchSize as a page size (for a cases when to many + * products SKUs were provided in parameters. + * Loaded products will be chached in the $this->idsBySku variable, but in the end of the method these storage will + * be truncated to $idsLimit quantity. + * As a result array with the products data will be returned with the following scheme: + * $data['product_sku']['link_field_value' => 'product_type'] + * + * @throws \Exception */ public function retrieveProductIdsBySkus(array $skus) { @@ -72,8 +92,16 @@ public function retrieveProductIdsBySkus(array $skus) $linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) ->getLinkField(); - foreach ($collection as $item) { - $this->idsBySku[strtolower(trim($item->getSku()))][$item->getData($linkField)] = $item->getTypeId(); + $collection->setPageSize($this->batchSize); + $pages = $collection->getLastPageNumber(); + for ($currentPage = 1; $currentPage <= $pages; $currentPage++) { + $collection->setCurPage($currentPage); + foreach ($collection->getItems() as $item) { + $sku = strtolower(trim($item->getSku())); + $itemIdentifier = $item->getData($linkField); + $this->idsBySku[$sku][$itemIdentifier] = $item->getTypeId(); + } + $collection->clear(); } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 5b420c33b3184..90f55cd44bdb9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -486,8 +486,20 @@ public function getProductsPosition($category) $this->getCategoryProductTable(), ['product_id', 'position'] )->where( - 'category_id = :category_id' + "{$this->getTable('catalog_category_product')}.category_id = ?", + $category->getId() ); + $websiteId = $category->getStore()->getWebsiteId(); + if ($websiteId) { + $select->join( + ['product_website' => $this->getTable('catalog_product_website')], + "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id", + [] + )->where( + 'product_website.website_id = ?', + $websiteId + ); + } $bind = ['category_id' => (int)$category->getId()]; return $this->getConnection()->fetchPairs($select, $bind); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php index 4562aa0b0e75f..618abda0a942d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php @@ -322,7 +322,9 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr ['e' => $this->getTable('catalog_category_entity')], 'main_table.category_id=e.entity_id', [] - )->where('e.entity_id = :entity_id OR e.path LIKE :c_path'); + )->where( + '(e.entity_id = :entity_id OR e.path LIKE :c_path)' + ); if ($websiteId) { $select->join( ['w' => $this->getProductWebsiteTable()], diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php index 9db2c8248ce52..05950531e2178 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php @@ -699,8 +699,20 @@ public function getProductsPosition($category) $this->getTable('catalog_category_product'), ['product_id', 'position'] )->where( - 'category_id = :category_id' + "{$this->getTable('catalog_category_product')}.category_id = ?", + $category->getId() ); + $websiteId = $category->getStore()->getWebsiteId(); + if ($websiteId) { + $select->join( + ['product_website' => $this->getTable('catalog_product_website')], + "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id", + [] + )->where( + 'product_website.website_id = ?', + $websiteId + ); + } $bind = ['category_id' => (int)$category->getId()]; return $this->getConnection()->fetchPairs($select, $bind); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php index f11c975396f2e..cf5760b0c33a9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php @@ -118,9 +118,15 @@ private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositi $result = ['changed' => [], 'updated' => []]; $oldCategoryPositions = array_values($oldCategoryPositions); - $oldCategoryList = array_column($oldCategoryPositions, 'category_id'); foreach ($newCategoryPositions as $newCategoryPosition) { - $key = array_search($newCategoryPosition['category_id'], $oldCategoryList); + $key = false; + + foreach ($oldCategoryPositions as $oldKey => $oldCategoryPosition) { + if ((int)$oldCategoryPosition['category_id'] === (int)$newCategoryPosition['category_id']) { + $key = $oldKey; + break; + } + } if ($key === false) { $result['changed'][] = $newCategoryPosition; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 14ae38667d873..bd314c0192d38 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -290,6 +290,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $dimensionFactory; + /** + * @var \Magento\Framework\DataObject + */ + private $emptyItem; + /** * Collection constructor * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory @@ -550,7 +555,10 @@ protected function _prepareStaticFields() */ public function getNewEmptyItem() { - $object = parent::getNewEmptyItem(); + if (null === $this->emptyItem) { + $this->emptyItem = parent::getNewEmptyItem(); + } + $object = clone $this->emptyItem; if ($this->isEnabledFlat()) { $object->setIdFieldName($this->getEntity()->getIdFieldName()); } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml new file mode 100644 index 0000000000000..90ceb1e4a1f96 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssignImageRolesActionGroup"> + <arguments> + <argument name="image"/> + </arguments> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggleState('closed')}}" dependentSelector="{{AdminProductImagesSection.productImagesToggleState('open')}}" visible="false" stepKey="clickSectionImage"/> + <click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/> + <checkOption selector="{{AdminProductImagesSection.roleBase}}" stepKey="checkRoleBase"/> + <checkOption selector="{{AdminProductImagesSection.roleSmall}}" stepKey="checkRoleSmall"/> + <checkOption selector="{{AdminProductImagesSection.roleThumbnail}}" stepKey="checkRoleThumbnail"/> + <checkOption selector="{{AdminProductImagesSection.roleSwatch}}" stepKey="checkRoleSwatch"/> + <click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index 01b31430793cc..28587e6405e49 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -70,6 +70,7 @@ <!--Save product and see success message--> <actionGroup name="saveProductForm"> + <scrollToTopOfPage stepKey="scrollTopPageProduct"/> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." stepKey="seeSaveConfirmation"/> </actionGroup> @@ -121,7 +122,6 @@ <argument name="simpleProduct"/> </arguments> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> - <waitForPageLoad stepKey="waitForPageLoad"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductDropdown"/> <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="clickAddSimpleProduct"/> <fillField userInput="{{simpleProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillName"/> @@ -313,4 +313,19 @@ <click selector="{{AdminProductFormAdvancedPricingSection.save}}" stepKey="clickSaveProduct"/> <waitForPageLoad stepKey="waitForSave"/> </actionGroup> + + <!--Check tier price with a discount percentage on product--> + <actionGroup name="AssertDiscountsPercentageOfProducts"> + <arguments> + <argument name="amount" type="string" defaultValue="45"/> + </arguments> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForCustomerGroupPriceAddButton"/> + <grabValueFrom selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('0')}}" stepKey="grabProductTierPriceInput"/> + <assertEquals stepKey="assertProductTierPriceInput"> + <expectedResult type="string">{{amount}}</expectedResult> + <actualResult type="string">$grabProductTierPriceInput</actualResult> + </assertEquals> + </actionGroup> + </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index fd1b6add8391f..817da18f5f2b7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -179,7 +179,6 @@ </arguments> <!--TODO use other action group for filtering grid when MQE-539 is implemented --> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> - <waitForPageLoad time="60" stepKey="waitForPageLoadInitial"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> @@ -193,6 +192,17 @@ <click selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="confirmProductDelete"/> </actionGroup> + <actionGroup name="deleteProductByName" extends="deleteProductBySku"> + <arguments> + <argument name="sku" type="string" defaultValue=""/> + <argument name="name" type="string"/> + </arguments> + <remove keyForRemoval="fillProductSkuFilter"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{name}}" stepKey="fillProductSkuFilter" after="openProductFilters"/> + <remove keyForRemoval="seeProductSkuInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="{{name}}" stepKey="seeProductNameInGrid" after="clickApplyFilters"/> + </actionGroup> + <!--Open product for edit by clicking row X and column Y in product grid--> <actionGroup name="openProducForEditByClickingRowXColumnYInProductGrid"> <arguments> @@ -233,6 +243,7 @@ <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> <click selector="{{AdminProductGridSection.bulkActionOption('Change status')}}" stepKey="clickChangeStatusAction"/> <click selector="{{AdminProductGridSection.changeStatus('status')}}" stepKey="clickChangeStatusDisabled" parameterized="true"/> + <waitForPageLoad stepKey="waitForStatusToBeChanged"/> <see selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) have been updated." stepKey="seeSuccessMessage"/> <waitForLoadingMaskToDisappear stepKey="waitForMaskToDisappear"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial2"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml index 8f89a85e14892..ea2543cd5c2ab 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml @@ -11,7 +11,7 @@ <arguments> <argument name="product" defaultValue="product"/> </arguments> - <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductRow"/> + <click selector="{{AdminProductGridSection.productRowBySku(product.sku)}}" stepKey="clickOnProductRow"/> <waitForPageLoad time="30" stepKey="waitForProductPageLoad"/> <seeInField userInput="{{product.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="seeProductSkuOnEditProductPage"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 5c79c321c9431..ff3c5cb4403bf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -42,4 +42,24 @@ <data key="is_active">true</data> <var key="parent_id" entityType="category" entityKey="id" /> </entity> + <entity name="FirstLevelSubCat" type="category"> + <data key="name" unique="suffix">FirstLevelSubCategory</data> + <data key="name_lwr" unique="suffix">subcategory</data> + </entity> + <entity name="SecondLevelSubCat" type="category"> + <data key="name" unique="suffix">SecondLevelSubCategory</data> + <data key="name_lwr" unique="suffix">subcategory</data> + </entity> + <entity name="ThirdLevelSubCat" type="category"> + <data key="name" unique="suffix">ThirdLevelSubCategory</data> + <data key="name_lwr" unique="suffix">subcategory</data> + </entity> + <entity name="FourthLevelSubCat" type="category"> + <data key="name" unique="suffix">FourthLevelSubCategory</data> + <data key="name_lwr" unique="suffix">subcategory</data> + </entity> + <entity name="FifthLevelCat" type="category"> + <data key="name" unique="suffix">FifthLevelCategory</data> + <data key="name_lwr" unique="suffix">category</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index e5b23fe3a3c34..74492ed416206 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -181,6 +181,9 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductUrlKey</requiredEntity> </entity> + <entity name="SetProductVisibilityHidden" type="product2"> + <data key="visibility">1</data> + </entity> <entity name="ProductImage" type="uploadImage"> <data key="title" unique="suffix">Image1</data> <data key="price">1.00</data> @@ -477,4 +480,30 @@ <requiredEntity type="product_extension_attribute">EavStock1</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <entity name="defaultSimpleProduct" type="product"> + <data key="name" unique="suffix">Test </data> + <data key="sku" unique="suffix">testsku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="price">560.00</data> + <data key="urlKey" unique="suffix">testurl-</data> + <data key="status">1</data> + <data key="quantity">25</data> + <data key="weight">1</data> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> + </entity> + <entity name="ProductWithLongNameSku" extends="ApiSimpleProduct"> + <data key="name" unique="suffix">Product With Long Name And Sku - But not too long</data> + <data key="sku" unique="suffix">Product With Long Name And Sku - But not too long</data> + </entity> + <entity name="Magento3" type="image"> + <data key="title" unique="suffix">Magento3</data> + <data key="price">1.00</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">magento3.jpg</data> + <data key="filename">magento3</data> + <data key="file_extension">jpg</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml index 9349e188430f4..f7d8abf8b2fea 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml @@ -8,7 +8,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> - <page name="AdminCategoryPage" url="catalog/category/" area="admin" module="Catalog"> + <page name="AdminCategoryPage" url="catalog/category/" area="admin" module="Magento_Catalog"> <section name="AdminCategorySidebarActionSection"/> <section name="AdminCategoryMainActionsSection"/> <section name="AdminCategorySidebarTreeSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml index 9a0dd8f5b387d..651ee54c08339 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml @@ -22,6 +22,7 @@ <element name="ContentTab" type="input" selector="input[name='name']"/> <element name="FieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> <element name="panelFieldControl" type="input" selector='//aside//div[@data-index="{{arg1}}"]/descendant::*[@name="{{arg2}}"]' parameterized="true"/> + <element name="productsInCategory" type="input" selector="div[data-index='assign_products']"/> </section> <section name="CategoryContentSection"> <element name="SelectFromGalleryBtn" type="button" selector="//label[text()='Select from Gallery']"/> @@ -38,6 +39,13 @@ <element name="FieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> <element name="filterPriceRangeUseConfig" type="checkbox" selector="input[name='use_config[filter_price_range]']"/> <element name="RequiredFieldIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index={{arg1}}]>.admin__field-label span'), ':after').getPropertyValue('content');" parameterized="true"/> + <element name="displayMode" type="button" selector="select[name='display_mode']"/> + <element name="anchor" type="checkbox" selector="input[name='is_anchor']"/> + <element name="productListCheckBox" type="checkbox" selector="input[name='use_config[available_sort_by]']" /> + <element name="productList" type="text" selector="select[name='available_sort_by']"/> + <element name="defaultProductLisCheckBox" type="checkbox" selector="input[name='use_config[default_sort_by]']"/> + <element name="defaultProductList" type="text" selector="select[name='default_sort_by']"/> + <element name="layeredNavigationPriceCheckBox" type="checkbox" selector="input[name='use_config[filter_price_range]']"/> </section> <section name="CatalogWYSIWYGSection"> <element name="ShowHideBtn" type="button" selector="#togglecategory_form_description"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml index acee714fe3ec7..e3d224904671b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml @@ -16,5 +16,13 @@ <element name="imageFileName" type="text" selector=".file-uploader-filename"/> <element name="removeImageButton" type="button" selector=".file-uploader-summary .action-remove"/> <element name="AddCMSBlock" type="select" selector="//*[@name='landing_page']"/> + <element name="description" type="input" selector="//*[@name='description']"/> + <element name="content" type="button" selector="div[data-index='content'"/> + <element name="categoryInTree" type="button" selector="//li[contains(@class, 'x-tree-node')]//div[contains(.,'{{categoryName}}') and contains(@class, 'no-active-category')]" parameterized="true" /> + <element name="categoryPageTitle" type="text" selector="h1.page-title" /> + <element name="activeCategoryInTree" type="button" selector="//li[contains(@class, 'x-tree-node')]//div[contains(.,'{{categoryName}}') and contains(@class, 'active-category')]" parameterized="true" /> + <element name="productTableColumnName" type="input" selector="#catalog_category_products_filter_name"/> + <element name="productTableRow" type="button" selector="#catalog_category_products_table tbody tr"/> + <element name="productSearch" type="button" selector="//button[@data-action='grid-filter-apply']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml index 40ea919226d6e..a449836fa08f4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml @@ -15,5 +15,6 @@ <element name="rowPrice" type="text" selector="#catalog_category_products_table tbody tr:nth-of-type({{row}}) .col-price" parameterized="true"/> <element name="rowPosition" type="input" selector="#catalog_category_products_table tbody tr:nth-of-type({{row}}) .col-position .position input" timeout="30" parameterized="true"/> <element name="productGridNameProduct" type="text" selector="//table[@id='catalog_category_products_table']//td[contains(., '{{productName}}')]" parameterized="true"/> + <element name="productVisibility" type="select" selector="//*[@name='product[visibility]']"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 249610568aec7..5791d0bfedab9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -167,6 +167,12 @@ <element name="checkIfArrowExpand" type="button" selector="//li[@id='root' and contains(@class,'jstree-closed')]" /> <element name="confirmDelete" type="button" selector=".action-primary.action-accept" /> </section> + <section name="ProductDescriptionWysiwygSection"> + <element name="EditArea" type="text" selector="#editorproduct_form_description .mce-edit-area"/> + </section> + <section name="ProductShortDescriptionWysiwygSection"> + <element name="EditArea" type="text" selector="#editorproduct_form_short_description .mce-edit-area"/> + </section> <section name="AdminProductFormAdvancedPricingSection"> <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 59228d57502f1..fe87c81ad6ac8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -8,6 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridSection"> + <element name="productRowBySku" type="block" selector="//div[@id='container']//tr//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]" parameterized="true" /> <element name="loadingMask" type="text" selector=".admin__data-grid-loading-mask[data-component*='product_listing']"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> <element name="column" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> @@ -15,6 +16,7 @@ <element name="productGridElement2" type="text" selector="#addselector" /> <element name="productGridRows" type="text" selector="table.data-grid tr.data-row"/> <element name="firstProductRow" type="text" selector="table.data-grid tr.data-row:first-of-type"/> + <element name="firstProductRowEditButton" type="button" selector="table.data-grid tr.data-row td .action-menu-item:first-of-type"/> <element name="productThumbnail" type="text" selector="table.data-grid tr:nth-child({{row}}) td.data-grid-thumbnail-cell > img" parameterized="true"/> <element name="productThumbnailBySrc" type="text" selector="img.admin__control-thumbnail[src*='{{pattern}}']" parameterized="true"/> <element name="productGridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml index eca0cb6f02ea1..89eb1ed678cc9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml @@ -16,6 +16,7 @@ <element name="removeImageButton" type="button" selector=".action-remove"/> <element name="modalOkBtn" type="button" selector="button.action-primary.action-accept"/> <element name="uploadProgressBar" type="text" selector=".uploader .file-row"/> + <element name="productImagesToggleState" type="button" selector="[data-index='gallery'] > [data-state-collapsible='{{status}}']" parameterized="true"/> <element name="nthProductImage" type="button" selector="#media_gallery_content > div:nth-child({{var}}) img.product-image" parameterized="true"/> <element name="nthRemoveImageBtn" type="button" selector="#media_gallery_content > div:nth-child({{var}}) button.action-remove" parameterized="true"/> @@ -32,4 +33,4 @@ <element name="isThumbnailSelected" type="button" selector="//div[contains(@class, 'field-image-role')]//ul/li[contains(@class, 'selected')]/label[normalize-space(.) = 'Thumbnail']"/> <element name="isSwatchSelected" type="button" selector="//div[contains(@class, 'field-image-role')]//ul/li[contains(@class, 'selected')]/label[normalize-space(.) = 'Swatch']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml index 54b289a89c705..4dcda8dcd41ae 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontMessagesSection"> <element name="success" type="text" selector="div.message-success.success.message"/> <element name="error" type="text" selector="div.message-error.error.message"/> + <element name="noticeMessage" type="text" selector="div.message-notice"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml index 83c3ca5348606..45e0b03e8d995 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductMediaSection"> <element name="imageFile" type="text" selector="//*[@class='product media']//img[contains(@src, '{{filename}}')]" parameterized="true"/> + <element name="productImageActive" type="text" selector=".product.media div[data-active=true] > img[src*='{{filename}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml index c87af1224ed30..e9c8f53f97e5f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml @@ -9,14 +9,18 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductPageSection"> - <element name="qtyInput" type="button" selector="input.input-text.qty"/> + <element name="qtyInput" type="button" selector="input.input-text.qty" timeout="30"/> <element name="addToCartBtn" type="button" selector="button.action.tocart.primary" timeout="30"/> - <element name="successMsg" type="button" selector="div.message-success"/> - <element name="errorMsg" type="button" selector="div.message-error"/> + <element name="successMsg" type="button" selector="div.message-success" timeout="30"/> + <element name="errorMsg" type="button" selector="div.message-error" timeout="30"/> <element name="alertMessage" type="text" selector=".page.messages [role=alert]"/> - <element name="messagesBlock" type="text" selector=".page.messages"/> + <element name="messagesBlock" type="text" selector=".page.messages" timeout="30"/> <element name="addToWishlist" type="button" selector="//a[@class='action towishlist']" timeout="30"/> <element name="customTextOptionInput" type="input" selector=".input-text.product-custom-option"/> <element name="charCounter" type="text" selector=".character-counter"/> + <element name="tax" type="input" selector=".totals-tax .amount .price"/> + <element name="subTotal" type="input" selector="span[data-th='Subtotal']"/> + <element name="shipping" type="input" selector="span[data-th='Shipping']"/> + <element name="orderTotal" type="input" selector=".grand.totals .amount .price"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml index 36d9dfeedc5ec..545e7c10379bf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml @@ -269,4 +269,56 @@ <actualResult type="variable">grabTextFromMiniCartSubtotalField2</actualResult> </assertEquals> </test> + <test name="AdminApplyTierPriceToProductWithPercentageDiscountTest"> + <annotations> + <features value="Catalog"/> + <stories value="MC-5517 - System tries to save 0 in Advanced Pricing which is invalid for Discount field"/> + <title value="You should be able to apply tier price to a product with float percent discount."/> + <description value="You should be able to apply tier price to a product with float percent discount."/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-96881"/> + <group value="product"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad time="30" stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct1"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForCustomerGroupPriceAddButton"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroupAllGroupsQty1PriceDiscountAndpercent"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="1" stepKey="fillProductTierPriceQtyInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceValueTypeSelect('0')}}" userInput="Discount" stepKey="selectProductTierPriceValueType"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('0')}}" userInput="0.1" stepKey="selectProductTierPricePriceInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.sku$$)}}" stepKey="goProductPageOnStorefront"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('99.90')}}" stepKey="assertProductFinalPriceProductPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceProductPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceOld('100')}}" stepKey="assertRegularPriceAmountProductPage"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('99.90')}}" stepKey="assertProductFinalPriceCategoryPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceLabelCategoryPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceOld('100')}}" stepKey="assertRegularPriceAmountCategoryPage"/> + </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml new file mode 100644 index 0000000000000..9115004ad9585 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithAnchorFieldTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create anchor subcategory with all fields"/> + <description value="Login as admin and create anchor subcategory with all fields"/> + <testCaseId value="MC-5267"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultBlock" stepKey="createDefaultCMSBlock"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct" /> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createDefaultCMSBlock" stepKey="deleteDefaultCMSBlock"/> + <deleteData stepKey="deleteSimpleProduct" createDataKey="simpleProduct"/> + </after> + <!--Create SubCategory--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <!--Select Content and fill the options--> + <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent"/> + <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent"/> + <scrollTo selector="{{AdminCategoryContentSection.AddCMSBlock}}" x="0" y="-80" stepKey="scrollToAddCMSBlock"/> + <selectOption selector="{{AdminCategoryContentSection.AddCMSBlock}}" userInput="$$createDefaultCMSBlock.title$$" stepKey="selectCMSBlock"/> + <!--Select Display Setting and fill the options--> + <scrollTo selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" x="0" y="-80" stepKey="scrollToDisplaySetting"/> + <click selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" stepKey="selectDisplaySetting"/> + <selectOption selector="{{CategoryDisplaySettingsSection.displayMode}}" userInput="PRODUCTS_AND_PAGE" stepKey="selectdisplayMode"/> + <checkOption selector="{{CategoryDisplaySettingsSection.anchor}}" stepKey="enableAnchor"/> + <click selector="{{CategoryDisplaySettingsSection.productListCheckBox}}" stepKey="enableTheAvailableProductList"/> + <selectOption selector="{{CategoryDisplaySettingsSection.productList}}" parameterArray="['Position', 'Product Name', 'Price']" stepKey="selectPrice"/> + <scrollTo selector="{{CategoryDisplaySettingsSection.defaultProductLisCheckBox}}" x="0" y="-80" stepKey="scrollToDefaultProductList"/> + <click selector="{{CategoryDisplaySettingsSection.defaultProductLisCheckBox}}" stepKey="enableTheDefaultProductList"/> + <selectOption selector="{{CategoryDisplaySettingsSection.defaultProductList}}" userInput="name" stepKey="selectProductName"/> + <scrollTo selector="{{CategoryDisplaySettingsSection.layeredNavigationPriceCheckBox}}" x="0" y="-80" stepKey="scrollToLayeredNavPrice"/> + <click selector="{{CategoryDisplaySettingsSection.layeredNavigationPriceCheckBox}}" stepKey="enableLayeredNavigationPrice"/> + <fillField selector="{{CategoryDisplaySettingsSection.layeredNavigationPriceInput}}" userInput="5.5" stepKey="fillThePrice"/> + <!--Search the products and select the category products--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="$$simpleProduct.name$$" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> + <!--Verify the Category Title--> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + <!--Verify Product in store front page--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name_lwr)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seeCategoryPageTitle"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeCategoryOnNavigation"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + <seeElement selector="{{StorefrontCategoryMainSection.productLinkByHref($$simpleProduct.urlKey$$)}}" stepKey="seeProductInCategory"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml new file mode 100644 index 0000000000000..e8c6da476a3d6 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithCustomRootCategoryTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create category in the custom root category that is used for custom website"/> + <description value="Login as admin and create a root category with nested sub category and verify category in store front "/> + <testCaseId value="MC-5272"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="navigateToStoresIndex"/> + <waitForPageLoad stepKey="waitStoreIndexPageLoad" /> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <actionGroup ref="DeleteCategory" stepKey="deleteCreatedNewRootCategory"> + <argument name="categoryEntity" value="NewRootCategory"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <!--Create Root Category--> + <actionGroup ref="AdminCreateRootCategory" stepKey="createNewRootCategory"> + <argument name="categoryEntity" value="NewRootCategory"/> + </actionGroup> + <!--Create subcategory--> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(NewRootCategory.name)}}" stepKey="clickOnCreatedNewRootCategory"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> + <actionGroup ref="CreateCategory" stepKey="createSubcategory"> + <argument name="categoryEntity" value="SimpleSubCategory"/> + </actionGroup> + <!--Create a Store--> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + <!--Create a Store View--> + <click selector="{{AdminStoresMainActionsSection.createStoreViewButton}}" stepKey="selectCreateStoreView"/> + <click selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="clickDropDown"/> + <selectOption userInput="{{customStore.name}}" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreViewStatus"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreSection.storeNameTextField}}" stepKey="fillStoreViewName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreSection.storeCodeTextField}}" stepKey="fillStoreViewCode"/> + <selectOption selector="{{AdminNewStoreSection.statusDropdown}}" userInput="Enabled" stepKey="enableStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreViewButton"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal" /> + <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarning" /> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="dismissModal" /> + <waitForElementNotVisible selector="{{AdminNewStoreViewActionsSection.loadingMask}}" stepKey="waitForElementVisible"/> + <!--Go to store front page--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad"/> + <!--Verify subcategory displayed in store front page--> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="selectMainWebsite"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="selectCustomStore"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="seeSubCategoryInStoreFrontPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml new file mode 100644 index 0000000000000..530bafaef24c2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithFiveNestingTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create category with five nesting"/> + <description value="Login as admin and create nested sub category and verify the subcategory displayed in store front page "/> + <testCaseId value="MC-5271"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="goToCategoryPage"/> + <waitForPageLoad time="60" stepKey="waitForCategoryPageLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(FirstLevelSubCat.name)}}" stepKey="clickCategoryLink"/> + <click selector="{{AdminCategoryMainActionsSection.DeleteButton}}" stepKey="clickDelete"/> + <waitForElementVisible selector="{{AdminCategoryModalSection.message}}" stepKey="waitForConfirmationModal"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="Are you sure you want to delete this category?" stepKey="seeDeleteConfirmationMessage"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="confirmDelete"/> + <waitForPageLoad time="60" stepKey="waitForDeleteToFinish"/> + <see selector="You deleted the category." stepKey="seeDeleteSuccess"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories"/> + <dontSee selector="{{AdminCategorySidebarTreeSection.categoryInTree(FirstLevelSubCat.name)}}" stepKey="dontSeeCategoryInTree"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Create Category with Five Nesting --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <!--Create Nested First Category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FirstLevelSubCat.name}}" stepKey="fillFirstSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFirstSubCategory"/> + <waitForPageLoad stepKey="waitForSFirstSubCategorySaved"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <!--Create Nested Second Sub Category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton1"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SecondLevelSubCat.name}}" stepKey="fillSecondSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSecondSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategory"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage1"/> + <!--Create Nested Third Sub Category/>--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton2"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{ThirdLevelSubCat.name}}" stepKey="fillThirdSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveThirdSubCategory"/> + <waitForPageLoad stepKey="waitForThirdCategorySaved"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage2"/> + <!--Create Nested fourth Sub Category />--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton3"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FourthLevelSubCat.name}}" stepKey="fillFourthSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFourthSubCategory"/> + <waitForPageLoad stepKey="waitForFourthCategorySaved"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage3"/> + <!--Create Nested fifth Sub Category />--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton4"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FifthLevelCat.name}}" stepKey="fillFifthSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFifthLevelCategory"/> + <waitForPageLoad stepKey="waitForFifthCategorySaved"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage4"/> + <amOnPage url="/{{FirstLevelSubCat.name}}/{{SecondLevelSubCat.name}}/{{ThirdLevelSubCat.name}}/{{FourthLevelSubCat.name}}/{{FifthLevelCat.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad"/> + <!--<Verify category displayed in store front page--> + <grabMultiple selector=".breadcrumbs li" stepKey="breadcrumbs"/> + <assertEquals stepKey="verifyTheCategoryInStoreFrontPage"> + <expectedResult type="array">['Home', {{FirstLevelSubCat.name}}, {{SecondLevelSubCat.name}}, {{ThirdLevelSubCat.name}}, {{FourthLevelSubCat.name}}, {{FifthLevelCat.name}} ]</expectedResult> + <actualResult type="variable">breadcrumbs</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml new file mode 100644 index 0000000000000..96f945da138b0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithInactiveCategoryTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create disabled subcategory"/> + <description value="Login as admin and create category with inactivated enable category option"/> + <testCaseId value="MC-5268"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create In active Category --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="disableCategory"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.IncludeInMenu}}" stepKey="enableIncludeInMenu"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="dontCategoryIsChecked"/> + <!--Verify InActive Category is created--> + <seeElement selector="{{AdminCategoryContentSection.categoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> + <!--Verify Category is not listed store front page--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="dontSeeCategoryOnStoreFrontPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml new file mode 100644 index 0000000000000..c983089163f78 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithInactiveIncludeInMenuTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create not included in menu subcategory"/> + <description value="Login as admin and create category with inactivated include in menu option"/> + <testCaseId value="MC-5269"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Create Category with not included in menu Subcategory --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="disableIncludeInMenu"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageSaved"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + <!--Verify Category is created/>--> + <seeElement selector="{{AdminCategoryContentSection.activeCategoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> + <!--Verify Category in store front page menu/>--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seeCategoryPageTitle"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="dontSeeCategoryOnNavigation"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml new file mode 100644 index 0000000000000..7c24a8aba27bd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithProductsGridFilter"> + <annotations> + <stories value="Create categories"/> + <title value="Apply category products grid filter"/> + <description value="Login as admin and create default product and product with grid filter"/> + <testCaseId value="MC-5273"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"/> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct1"> + <argument name="product" value="defaultSimpleProduct"/> + </actionGroup> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct2"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <!--Create Default Product--> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddDefaultProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{SimpleProduct.name}}" stepKey="fillDefaultProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{SimpleProduct.sku}}" stepKey="fillDefaultProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{SimpleProduct.price}}" stepKey="fillDefaultProductPrice"/> + <scrollTo selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="scrollToSearchEngine"/> + <click selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="selectSearchEngineOptimization"/> + <fillField selector="{{AdminProductFormBundleSection.urlKey}}" userInput="{{SimpleProduct.urlKey}}" stepKey="fillUrlKey"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveDefaultProduct"/> + <waitForPageLoad stepKey="waitForPDefaultProductSaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="successMessageYouSavedTheProductIsShown"/> + <!--Create product with grid filter Not Visible Individually--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="ProductList"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddFilterProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{defaultSimpleProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{defaultSimpleProduct.price}}" stepKey="fillProductPrice"/> + <selectOption selector="{{AdminCategoryProductsGridSection.productVisibility}}" userInput="Not Visible Individually" stepKey="selectProductVisibility"/> + <scrollTo selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="scrollToSearchEngineOptimization"/> + <click selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="selectSearchEngineOptimization1"/> + <fillField selector="{{AdminProductFormBundleSection.urlKey}}" userInput="{{defaultSimpleProduct.urlKey}}" stepKey="fillUrlKey1"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad stepKey="waitForProductSaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown"/> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <!--Create sub category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> + <!--Select the default product and product with grid filter--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="{{SimpleProduct.name}}" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromRow"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="selectDefaultProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton1"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectDefaultProductFromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="WaitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="successMessageYouSavedTheCategory"/> + <!--Verify product with grid filter is not not visible--> + <amOnPage url="{{StorefrontProductPage.url(defaultSimpleProduct.urlKey)}}" stepKey="seeOnProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageToLoad"/> + <dontSee selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="dontSeeProductInStoreFrontPage"/> + <!--Verify product in Store Front Page--> + <amOnPage url="{{StorefrontProductPage.url(SimpleProduct.urlKey)}}" stepKey="seeDefaultProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageToLoad1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{SimpleProduct.name}}" stepKey="seeProductInStoreFrontPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml new file mode 100644 index 0000000000000..1b6c9707b0656 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCategoryWithRequiredFieldsTest"> + <annotations> + <stories value="Create categories"/> + <title value="Create Category from Category page with Required Fields Only"/> + <description value="Login as an admin and create a category with required fields."/> + <testCaseId value="MC-5265"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create subcategory with required fields --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <!-- Verify success message --> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <!-- Verify subcategory created with required fields --> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + <seeElement selector="{{AdminCategoryContentSection.activeCategoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> + <!--Verify Category is listed in store front page menu/>--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seeCategoryPageTitle"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml new file mode 100644 index 0000000000000..f98f9acc46961 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateRootCategoryRequiredFieldsTest"> + <annotations> + <stories value="Create categories"/> + <features value="Catalog"/> + <title value="Create Root Category from Category Page"/> + <description value="Create Root Category from Category Page"/> + <testCaseId value="MC-5263"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="LoginToAdminPanel"/> + </before> + <after> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"> + <argument name="categoryEntity" value="_defaultCategory" /> + </actionGroup> + <actionGroup ref="logout" stepKey="logout" /> + </after> + + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="OpenAdminCatergoryIndexPage"/> + <click selector="{{AdminCategorySidebarActionSection.AddRootCategoryButton}}" stepKey="ClickOnAddRootButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="FillCategoryField"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="EnableCheckOption"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="ClickSaveButton"/> + <waitForPageLoad stepKey="WaitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="AssertSuccessMessage"/> + <seeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="SeeCheckBoxisSelected"/> + <seeInField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="SeedFieldInput"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml index 9e323745835c2..e0e214342ad72 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml @@ -19,19 +19,19 @@ </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create website, Sore adn Store View--> - <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateWebsite"> - <argument name="newWebsiteName" value="secondWebsite"/> - <argument name="websiteCode" value="second_website"/> + <!--Create website, Store and Store View--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{secondCustomWebsite.name}}"/> + <argument name="websiteCode" value="{{secondCustomWebsite.code}}"/> </actionGroup> - <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="adminCreateStore"> - <argument name="website" value="secondWebsite"/> - <argument name="storeGroupName" value="secondStore"/> - <argument name="storeGroupCode" value="second_store"/> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStoreGroup"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> </actionGroup> - <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="adminCreateStoreView"> - <argument name="StoreGroup" value="customStoreTierPrice"/> - <argument name="customStore" value="customStoreView"/> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> </actionGroup> <!--Create Simple Product and Category --> @@ -50,54 +50,46 @@ </createData> <!-- Set filter to product name and product0 not assigned to any website--> - <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="filterGroupedProductOptions"> - <argument name="product" value="_defaultProduct"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct0"> + <argument name="product" value="$$createProduct0$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="clickOpenProductForEdit0"> + <argument name="product" value="$$createProduct0$$"/> </actionGroup> - - <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct0.name$$')}}" - stepKey="clickOpenProductForEdit"/> - <waitForPageLoad time="30" stepKey="waitForProductEditOpen"/> - <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToWebsitesSection"/> <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenWebsiteSection"/> <waitForPageLoad stepKey="waitForToOpenedWebsiteSection"/> <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> <!-- Set filter to product name and product2 in website 2 only --> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> - <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad"/> - <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct2.name$$')}}" - stepKey="clickOpenProductForEdit1"/> - <waitForPageLoad time="30" stepKey="waitForProductEditOpen1"/> - + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct2"> + <argument name="product" value="$$createProduct2$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="clickOpenProductForEdit2"> + <argument name="product" value="$$createProduct2$$"/> + </actionGroup> <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{secondCustomWebsite.name}}"/> </actionGroup> <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite1"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct1"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage1"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct2"/> <!-- Set filter to product name and product12 assigned to both websites 1 and 2 --> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex1"/> - <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad1"/> - <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct12.name$$')}}" - stepKey="clickOpenProductForEdit2"/> - <waitForPageLoad time="30" stepKey="waitForProductEditOpen2"/> - + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct12"> + <argument name="product" value="$$createProduct12$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="clickOpenProductForEdit12"> + <argument name="product" value="$$createProduct12$$"/> + </actionGroup> <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites1"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{secondCustomWebsite.name}}"/> </actionGroup> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct2"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage2"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct3"/> </before> <after> <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> - <argument name="websiteName" value="secondWebsite"/> + <argument name="websiteName" value="{{secondCustomWebsite.name}}"/> </actionGroup> <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> <deleteData createDataKey="createProduct0" stepKey="deleteProduct"/> @@ -109,10 +101,13 @@ </after> <!-- Step 1-2: Open Category page and Set scope selector to All Store Views--> <amOnPage url="{{AdminCategoryPage.url}}" stepKey="goToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad"/> <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" stepKey="clickCategoryName"/> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection"/> + <grabTextFrom selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" + stepKey="grabTextFromCategory"/> + <assertRegExp expected="/\(4\)$/" expectedType="string" actual="$grabTextFromCategory" actualType="variable" + message="wrongCountProductOnAllStoreViews" stepKey="checkCountProducts"/> <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct0.name$$)}}" userInput="$$createProduct0.name$$" stepKey="seeProductName"/> <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct1.name$$)}}" @@ -124,17 +119,13 @@ <!-- Step 3: Set scope selector to Website1( Storeview for the Website 1) --> <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" - stepKey="clickStoresList"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption('Default Store View')}}" - stepKey="clickStoreView"/> - <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForPopup1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" stepKey="clickActionAccept"/> - <waitForElementNotVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForNotVisibleModalAccept"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad2"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="swichToDefaultStoreView"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <grabTextFrom selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" + stepKey="grabTextFromCategory1"/> + <assertRegExp expected="/\(2\)$/" expectedType="string" actual="$grabTextFromCategory1" actualType="variable" + message="wrongCountProductOnWebsite1" stepKey="checkCountProducts1"/> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection1"/> <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct1.name$$)}}" userInput="$$createProduct1.name$$" stepKey="seeProductName4"/> @@ -146,21 +137,16 @@ <dontSee selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" userInput="$$createProduct2.name$$" stepKey="dontSeeProductName1"/> - <!-- Step 4: Set scope selector to Website2 ( StopreView for Website 2) --> + <!-- Step 4: Set scope selector to Website2 ( StoreView for Website 2) --> <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" - stepKey="clickStoresList1"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad3"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption('secondStoreView')}}" - stepKey="clickStoreView1"/> - <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForPopup2"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="clickActionAccept1"/> - <waitForElementNotVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForNotVisibleModalAccept1"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad4"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="swichToSecondStoreView"> + <argument name="storeView" value="SecondStoreUnique.name"/> + </actionGroup> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection2"/> + <grabTextFrom selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" + stepKey="grabTextFromCategory2"/> + <assertRegExp expected="/\(2\)$/" expectedType="string" actual="$grabTextFromCategory2" actualType="variable" + message="wrongCountProductOnWebsite2" stepKey="checkCountProducts2"/> <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" userInput="$$createProduct2.name$$" stepKey="seeProductName6"/> <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct12.name$$)}}" diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml new file mode 100644 index 0000000000000..b9a5a31ad2168 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductImageAssignmentForMultipleStoresTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product image assignment for multiple stores"/> + <title value="Product image assignment for multiple stores"/> + <description value="Product image assignment for multiple stores"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-58718"/> + <group value="product"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <!-- Login Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create Store View English --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Create Store View France --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <!-- Create Category and Simple Product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + </before> + <after> + <!-- Delete Store View English --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Delete Store View France --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <!-- Clear Filter Store --> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="resetFiltersOnStorePage"/> + <!-- Delete Category and Simple Product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Clear Filter Product --> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!-- Search Product and Open Edit --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!-- Switch to the English store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchStoreViewEnglishProduct"> + <argument name="storeView" value="customStoreEN.name"/> + </actionGroup> + + <!-- Upload Image English --> + <actionGroup ref="addProductImage" stepKey="uploadImageEnglish"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + + <!-- Switch to the French store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchStoreViewFrenchProduct"> + <argument name="storeView" value="customStoreFR.name"/> + </actionGroup> + + <!-- Upload Image French --> + <actionGroup ref="addProductImage" stepKey="uploadImageFrench"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesActionGroup" stepKey="assignImageRole1"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct2"/> + + <!-- Switch to the All store view --> + <actionGroup ref="AdminSwitchToAllStoreViewActionGroup" stepKey="switchAllStoreViewProduct"/> + + <!-- Upload Image All Store View --> + <actionGroup ref="addProductImage" stepKey="uploadImageAllStoreView"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesActionGroup" stepKey="assignImageRole"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Change any product data product description --> + <click selector="{{AdminProductContentSection.sectionHeader}}" stepKey="openDescriptionDropDown"/> + <fillField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="This is the long description" stepKey="fillLongDescription"/> + <fillField selector="{{AdminProductContentSection.shortDescriptionTextArea}}" userInput="This is the short description" stepKey="fillShortDescription"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Go to Product Page and see Default Store View--> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="goToDefaultStorefrontProductPage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(TestImageNew.filename)}}" stepKey="seeActiveImageDefault"/> + + <!-- English Switch Store View and see English Store View --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewEnglish"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad time="30" stepKey="waitForCategoryPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageBySrc(ProductImage.fileName)}}" stepKey="seeThumb"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad time="30" stepKey="waitForProductPage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(ProductImage.filename)}}" stepKey="seeActiveImageEnglish"/> + + <!-- Switch France Store View and see France Store View --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewFrance"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="openCategoryPage1"/> + <waitForPageLoad time="30" stepKey="waitForCategoryPage1"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageBySrc(Magento3.fileName)}}" stepKey="seeThumb1"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProduct.name$$)}}" stepKey="openProductPage1"/> + <waitForPageLoad time="30" stepKey="waitForProductPage1"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(Magento3.filename)}}" stepKey="seeActiveImageFrance"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml index 281bad4e49591..7da668df59022 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml @@ -108,6 +108,10 @@ </actionGroup> <actionGroup ref="ClearProductsFilterActionGroup" stepKey="ClearProductsFilterActionGroup"/> + <!--Flush cache--> + <magentoCLI command="cache:flush" stepKey="cleanCache"/> + + <!--Edit customer info--> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="OpenEditCustomerFrom"> <argument name="customer" value="$$customer$$"/> @@ -147,9 +151,9 @@ <!--Create new order--> <actionGroup ref="navigateToNewOrderPageExistingCustomer" stepKey="CreateNewOrder"> <argument name="customer" value="Simple_US_Customer"/> + <argument name="storeView" value="customStoreView"/> </actionGroup> - <click selector="{{OrdersGridSection.website('secondStoreView')}}" stepKey="ClickToSelectStore"/> - <waitForPageLoad stepKey="waitForPageOpened"/> + <click selector="{{OrdersGridSection.addProducts}}" stepKey="clickToAddProduct"/> <waitForPageLoad stepKey="waitForProductsOpened"/> <!--TEST CASE #1--> @@ -318,13 +322,17 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <createData entity="DefaultConfigCatalogPrice" stepKey="defaultConfigCatalogPrice"/> - <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> - <argument name="websiteName" value="secondWebsite"/> - </actionGroup> <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> <argument name="ruleName" value="ship"/> </actionGroup> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> + <argument name="websiteName" value="secondWebsite"/> + </actionGroup> <actionGroup ref="logout" stepKey="logout"/> + + <!--Do reindex and flush cache--> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </after> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml similarity index 92% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index c845a27773170..d96399738c80a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontPurchaseProductWithCustomOptions"> + <test name="StorefrontPurchaseProductWithCustomOptionsTest"> <annotations> <features value="Catalog"/> <stories value="Purchase a product with Custom Options of different types"/> @@ -17,6 +17,8 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-61717"/> <group value="Catalog"/> + <!-- skip due to MAGETWO-97424 --> + <group value="skip"/> </annotations> <before> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> @@ -53,6 +55,10 @@ <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptionsMultiselect(ProductOptionMultiSelect.title, ProductOptionValueMultiSelect1.price)}}" stepKey="checkMultiSelectProductOption"/> <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptionsData(ProductOptionDate.title, ProductOptionDate.price)}}" stepKey="checkDataProductOption"/> + <!--Generate year--> + <generateDate date="Now" format="Y" stepKey="year"/> + <generateDate date="Now" format="y" stepKey="shortYear"/> + <!-- Adding items to the checkout --> <fillField userInput="OptionField" selector="{{StorefrontProductInfoMainSection.productOptionFieldInput(ProductOptionField.title)}}" stepKey="fillProductOptionInputField"/> @@ -64,10 +70,10 @@ <selectOption userInput="{{ProductOptionValueMultiSelect1.price}}" selector="{{StorefrontProductInfoMainSection.productOptionSelect(ProductOptionMultiSelect.title)}}" stepKey="selectProductOptionMultiSelect"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDataMonth(ProductOptionDate.title)}}" stepKey="selectProductOptionDate"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDataDay(ProductOptionDate.title)}}" stepKey="selectProductOptionDate1"/> - <selectOption userInput="2018" selector="{{StorefrontProductInfoMainSection.productOptionDataYear(ProductOptionDate.title)}}" stepKey="selectProductOptionDate2"/> + <selectOption userInput="$year" selector="{{StorefrontProductInfoMainSection.productOptionDataYear(ProductOptionDate.title)}}" stepKey="selectProductOptionDate2"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeMonth(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeMonth"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeDay(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeDay"/> - <selectOption userInput="2018" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeYear(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeYear"/> + <selectOption userInput="$year" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeYear(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeYear"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeHour(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeHour"/> <selectOption userInput="00" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeMinute(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeMinute"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionTimeHour(ProductOptionTime.title)}}" stepKey="selectProductOptionTimeHour"/> @@ -100,8 +106,8 @@ <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeProductOptionValueRadioButtons1Input1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeProductOptionValueCheckboxInput1" /> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeproductAttributeOptionsMultiselect1Input1" /> - <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="Jan 1, 2018" stepKey="seeProductOptionDateAndTimeInput" /> - <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1/1/18, 1:00 AM" stepKey="seeProductOptionDataInput" /> + <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="Jan 1, $year" stepKey="seeProductOptionDateAndTimeInput" /> + <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeProductOptionDataInput" /> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1:00 AM" stepKey="seeProductOptionTimeInput" /> <!--Select shipping method--> <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> @@ -138,8 +144,8 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeAdminOrderproductAttributeOptionsMultiselect1" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, 2018" stepKey="seeAdminOrderProductOptionDateAndTime" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/18, 1:00 AM" stepKey="seeAdminOrderProductOptionData" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, $year" stepKey="seeAdminOrderProductOptionDateAndTime" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeAdminOrderProductOptionData" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1:00 AM" stepKey="seeAdminOrderProductOptionTime" /> <!-- Reorder and Checking the correctness of displayed custom options for user parameters on Order and correctness of displayed price Subtotal--> @@ -156,8 +162,8 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton11"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeAdminOrderproductAttributeOptionsMultiselect11" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, 2018" stepKey="seeAdminOrderProductOptionDateAndTime1" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/18, 1:00 AM" stepKey="seeAdminOrderProductOptionData1" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, $year" stepKey="seeAdminOrderProductOptionDateAndTime1" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeAdminOrderProductOptionData1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1:00 AM" stepKey="seeAdminOrderProductOptionTime1" /> <see selector="{{AdminOrderTotalSection.subTotal}}" userInput="{$finalProductPrice}" stepKey="seeOrderSubTotal"/> @@ -174,8 +180,8 @@ <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionRadiobutton.title, ProductOptionValueRadioButtons1.title)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeStorefontOrderProductOptionValueRadioButtons11"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionCheckbox.title, ProductOptionValueCheckbox.title)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeStorefontOrderProductOptionValueCheckbox1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionMultiSelect.title, ProductOptionValueMultiSelect1.title)}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeStorefontOrderproductAttributeOptionsMultiselect11" /> - <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDate.title, 'Jan 1, 2018')}}" userInput="Jan 1, 2018" stepKey="seeStorefontOrderProductOptionDateAndTime1" /> - <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDateTime.title, '1/1/18, 1:00 AM')}}" userInput="1/1/18, 1:00 AM" stepKey="seeStorefontOrderProductOptionData1" /> + <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDate.title, 'Jan 1, $year')}}" userInput="Jan 1, $year" stepKey="seeStorefontOrderProductOptionDateAndTime1" /> + <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDateTime.title, '1/1/$shortYear, 1:00 AM')}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeStorefontOrderProductOptionData1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionTime.title, '1:00 AM')}}" userInput="1:00 AM" stepKey="seeStorefontOrderProductOptionTime1" /> <!-- Delete product and category --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml index fcdb92d71f1f8..04cb813ec0efb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -33,6 +33,8 @@ <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Login Customer Storefront --> @@ -78,6 +80,9 @@ <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterClickNext"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> + <!-- Place Order --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectPaymentMethod"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml index 26dd94b19de64..29ed3af4f01d9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml @@ -32,16 +32,19 @@ <click selector="{{AdminProductFormSection.contentTab}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="waitForDescription" /> <seeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="TinyMCE4Description" /> - <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="waitForShortDescription" /> - <seeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="TinyMCE4ShortDescription" /> + <click selector="{{ProductDescriptionWysiwygSection.EditArea}}" stepKey="focusProductDescriptionWysiwyg"/> <executeJS function="tinyMCE.get('product_form_description').setContent('Hello World!');" stepKey="executeJSFillContent1"/> + <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="waitForShortDescription" /> + <seeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="TinyMCE4ShortDescription" /> + <click selector="{{ProductShortDescriptionWysiwygSection.EditArea}}" stepKey="focusProductShortDescriptionWysiwyg"/> <executeJS function="tinyMCE.get('product_form_short_description').setContent('Hello World! Short Content');" stepKey="executeJSFillContent2"/> + <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" y="-150" x="0" stepKey="scrollToDesShowHideBtn1" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" stepKey="clickShowHideBtn1" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertImageBtn}}" stepKey="waitForInsertImage1" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertImageBtn}}" userInput="Insert Image..." stepKey="seeInsertImage1"/> <dontSee selector="{{TinyMCESection.InsertWidgetBtn}}" stepKey="insertWidget1" /> <dontSee selector="{{TinyMCESection.InsertVariableBtn}}" stepKey="insertVariable1" /> - <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" stepKey="scrollToDesShowHideBtn" /> + <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.showHideBtn}}" stepKey="scrollToDesShowHideBtn2" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.showHideBtn}}" stepKey="clickShowHideBtn2" /> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertImageBtn}}" stepKey="waitForInsertImage2" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertImageBtn}}" userInput="Insert Image..." stepKey="seeInsertImage2"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php index da6b790fedfa6..7c2ec8abb768a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php @@ -18,6 +18,11 @@ class DefaultValidatorTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,8 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); + $config = [ [ 'label' => 'group label 1', @@ -51,7 +58,8 @@ protected function setUp() $configMock->expects($this->once())->method('getAll')->will($this->returnValue($config)); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\DefaultValidator( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -63,10 +71,10 @@ public function isValidTitleDataProvider() { $mess = ['option required fields' => 'Missed values for option required fields']; return [ - ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), [], true], - [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], + ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), [], true], + [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], ]; } @@ -79,15 +87,18 @@ public function isValidTitleDataProvider() * @param bool $result * @dataProvider isValidTitleDataProvider */ - public function testIsValidTitle($title, $type, $priceType, $product, $messages, $result) + public function testIsValidTitle($title, $type, $priceType, $price, $product, $messages, $result) { - $methods = ['getTitle', 'getType', 'getPriceType', '__wakeup', 'getProduct']; + $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); $valueMock->expects($this->any())->method('getType')->will($this->returnValue($type)); $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); - // $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); + $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); + + $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price)); + $this->assertEquals($result, $this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); } @@ -126,4 +137,43 @@ public function testIsValidFail($product) $this->assertFalse($this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); } + + /** + * Data provider for testValidationNegativePrice + * @return array + */ + public function validationPriceDataProvider() + { + return [ + ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 1])], + ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 0])], + ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 1])], + ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 0])] + ]; + } + + /** + * @param $title + * @param $type + * @param $priceType + * @param $price + * @param $product + * @dataProvider validationPriceDataProvider + */ + public function testValidationPrice($title, $type, $priceType, $price, $product) + { + $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; + $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); + $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); + $valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue($type)); + $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); + $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); + $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); + + $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price)); + + $messages = []; + $this->assertTrue($this->validator->isValid($valueMock)); + $this->assertEquals($messages, $this->validator->getMessages()); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php index 2de993c075514..e688da1c6aa16 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php @@ -18,6 +18,11 @@ class FileTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,8 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); + $config = [ [ 'label' => 'group label 1', @@ -53,7 +60,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\File( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -70,6 +78,15 @@ public function testIsValidSuccess() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(15)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(2)) + ->method('getNumber') + ->with($this->equalTo(15)) + ->will($this->returnValue(15)); $this->assertEmpty($this->validator->getMessages()); $this->assertTrue($this->validator->isValid($this->valueMock)); } @@ -87,6 +104,16 @@ public function testIsValidWithNegativeImageSize() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(-10)); $this->valueMock->expects($this->never())->method('getImageSizeY'); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(1)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); + $messages = [ 'option values' => 'Invalid option value', ]; @@ -107,6 +134,15 @@ public function testIsValidWithNegativeImageSizeY() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(-10)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(2)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php index b97783edf856c..7fad5592a2d21 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php @@ -18,6 +18,11 @@ class SelectTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,7 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); $config = [ [ 'label' => 'group label 1', @@ -53,7 +59,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods, []); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Select( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -69,6 +76,12 @@ public function testIsValidSuccess($expectedResult, array $value) $this->valueMock->expects($this->never())->method('getPriceType'); $this->valueMock->expects($this->never())->method('getPrice'); $this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value])); + if (isset($value['price'])) { + $this->localeFormatMock + ->expects($this->once()) + ->method('getNumber') + ->will($this->returnValue($value['price'])); + } $this->assertEquals($expectedResult, $this->validator->isValid($this->valueMock)); } @@ -117,6 +130,7 @@ public function testIsValidateWithInvalidOptionValues() ->method('getData') ->with('values') ->will($this->returnValue('invalid_data')); + $messages = [ 'option values' => 'Invalid option value', ]; @@ -159,6 +173,7 @@ public function testIsValidateWithInvalidData($priceType, $price, $title) $this->valueMock->expects($this->never())->method('getPriceType'); $this->valueMock->expects($this->never())->method('getPrice'); $this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value])); + $this->localeFormatMock->expects($this->any())->method('getNumber')->will($this->returnValue($price)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php index 4881154728ddc..a3e6189f74925 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php @@ -18,6 +18,11 @@ class TextTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,7 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); $config = [ [ 'label' => 'group label 1', @@ -53,7 +59,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Text( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -69,6 +76,10 @@ public function testIsValidSuccess() $this->valueMock->method('getPrice') ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(10)); + $this->localeFormatMock->expects($this->exactly(2)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); $this->assertTrue($this->validator->isValid($this->valueMock)); $this->assertEmpty($this->validator->getMessages()); } @@ -85,6 +96,15 @@ public function testIsValidWithNegativeMaxCharacters() $this->valueMock->method('getPrice') ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(-10)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(1)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php index c9288790ed6e1..a97f2281125a6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php @@ -152,6 +152,30 @@ public function testGet() $this->assertEquals(2, count($prices)); } + /** + * Test get method without tierprices. + * + * @return void + */ + public function testGetWithoutTierPrices() + { + $skus = ['simple', 'virtual']; + $this->tierPriceValidator + ->expects($this->once()) + ->method('validateSkus') + ->with($skus) + ->willReturn($skus); + $this->productIdLocator->expects($this->atLeastOnce()) + ->method('retrieveProductIdsBySkus') + ->with(['simple', 'virtual']) + ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); + $this->tierPricePersistence->expects($this->once())->method('get')->willReturn([]); + $this->tierPricePersistence->expects($this->never())->method('getEntityLinkField'); + $this->tierPriceFactory->expects($this->never())->method('create'); + $prices = $this->tierPriceStorage->get($skus); + $this->assertEmpty($prices); + } + /** * Test update method. * diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php index b730e12ca820b..b9cb82274c808 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php @@ -58,7 +58,16 @@ public function testRetrieveProductIdsBySkus() { $skus = ['sku_1', 'sku_2']; $collection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class) - ->setMethods(['getIterator', 'addFieldToFilter']) + ->setMethods( + [ + 'getItems', + 'addFieldToFilter', + 'setPageSize', + 'getLastPageNumber', + 'setCurPage', + 'clear' + ] + ) ->disableOriginalConstructor()->getMock(); $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) ->setMethods(['getSku', 'getData', 'getTypeId']) @@ -69,7 +78,11 @@ public function testRetrieveProductIdsBySkus() $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); $collection->expects($this->once())->method('addFieldToFilter') ->with(\Magento\Catalog\Api\Data\ProductInterface::SKU, ['in' => $skus])->willReturnSelf(); - $collection->expects($this->once())->method('getIterator')->willReturn(new \ArrayIterator([$product])); + $collection->expects($this->atLeastOnce())->method('getItems')->willReturn([$product]); + $collection->expects($this->atLeastOnce())->method('setPageSize')->willReturnSelf(); + $collection->expects($this->atLeastOnce())->method('getLastPageNumber')->willReturn(1); + $collection->expects($this->atLeastOnce())->method('setCurPage')->with(1)->willReturnSelf(); + $collection->expects($this->atLeastOnce())->method('clear')->willReturnSelf(); $this->metadataPool ->expects($this->once()) ->method('getMetadata') diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index c95b812705adc..9867fdf910219 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -549,6 +549,7 @@ public function testSetCategoryCollection() public function testGetCategory() { + $this->model->setData('category_ids', [10]); $this->category->expects($this->any())->method('getId')->will($this->returnValue(10)); $this->registry->expects($this->any())->method('registry')->will($this->returnValue($this->category)); $this->categoryRepository->expects($this->any())->method('get')->will($this->returnValue($this->category)); @@ -557,7 +558,8 @@ public function testGetCategory() public function testGetCategoryId() { - $this->category->expects($this->once())->method('getId')->will($this->returnValue(10)); + $this->model->setData('category_ids', [10]); + $this->category->expects($this->any())->method('getId')->will($this->returnValue(10)); $this->registry->expects($this->at(0))->method('registry'); $this->registry->expects($this->at(1))->method('registry')->will($this->returnValue($this->category)); @@ -565,6 +567,14 @@ public function testGetCategoryId() $this->assertEquals(10, $this->model->getCategoryId()); } + public function testGetCategoryIdWhenProductNotInCurrentCategory() + { + $this->model->setData('category_ids', [12]); + $this->category->expects($this->once())->method('getId')->will($this->returnValue(10)); + $this->registry->expects($this->any())->method('registry')->will($this->returnValue($this->category)); + $this->assertFalse($this->model->getCategoryId()); + } + public function testGetIdBySku() { $this->resource->expects($this->once())->method('getIdBySku')->will($this->returnValue(5)); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php index 9e2b196602993..5a1a5906ec4b9 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; use Magento\Catalog\Model\ResourceModel\Product\CategoryLink; @@ -129,9 +130,20 @@ public function testSaveCategoryLinks($newCategoryLinks, $dbCategoryLinks, $affe ); } + $expectedResult = []; + + foreach ($affectedIds as $type => $ids) { + $expectedResult = array_merge($expectedResult, $ids); + // Verify if the correct insert, update and/or delete actions are performed: + $this->setupExpectationsForConnection($type, $ids); + } + $actualResult = $this->model->saveCategoryLinks($product, $newCategoryLinks); + sort($actualResult); - $this->assertEquals($affectedIds, $actualResult); + sort($expectedResult); + + $this->assertEquals($expectedResult, $actualResult); } /** @@ -151,7 +163,11 @@ public function getCategoryLinksDataProvider() ['category_id' => 3, 'position' => 10], ['category_id' => 4, 'position' => 20], ], - [], // Nothing to update - data not changed + [ + 'update' => [], + 'insert' => [], + 'delete' => [], + ], ], [ [ @@ -162,7 +178,11 @@ public function getCategoryLinksDataProvider() ['category_id' => 3, 'position' => 10], ['category_id' => 4, 'position' => 20], ], - [3, 4, 5], // 4 - updated position, 5 - added, 3 - deleted + [ + 'update' => [4], + 'insert' => [5], + 'delete' => [3], + ], ], [ [ @@ -173,7 +193,11 @@ public function getCategoryLinksDataProvider() ['category_id' => 3, 'position' => 10], ['category_id' => 4, 'position' => 20], ], - [3, 4], // 3 - updated position, 4 - deleted + [ + 'update' => [3], + 'insert' => [], + 'delete' => [4], + ], ], [ [], @@ -181,8 +205,80 @@ public function getCategoryLinksDataProvider() ['category_id' => 3, 'position' => 10], ['category_id' => 4, 'position' => 20], ], - [3, 4], // 3, 4 - deleted + [ + 'update' => [], + 'insert' => [], + 'delete' => [3, 4], + ], ], + [ + [ + ['category_id' => 3, 'position' => 10], + ['category_id' => 4, 'position' => 20], + ], + [ + ['category_id' => 3, 'position' => 20], // swapped positions + ['category_id' => 4, 'position' => 10], // swapped positions + ], + [ + 'update' => [3, 4], + 'insert' => [], + 'delete' => [], + ], + ] ]; } + + /** + * @param $type + * @param $ids + */ + private function setupExpectationsForConnection($type, $ids): void + { + switch ($type) { + case 'insert': + $this->connectionMock + ->expects($this->exactly(empty($ids) ? 0 : 1)) + ->method('insertArray') + ->with( + $this->anything(), + $this->anything(), + $this->callback(function ($data) use ($ids) { + $foundIds = []; + foreach ($data as $row) { + $foundIds[] = $row['category_id']; + } + return $ids === $foundIds; + }) + ); + break; + case 'update': + $this->connectionMock + ->expects($this->exactly(empty($ids) ? 0 : 1)) + ->method('insertOnDuplicate') + ->with( + $this->anything(), + $this->callback(function ($data) use ($ids) { + $foundIds = []; + foreach ($data as $row) { + $foundIds[] = $row['category_id']; + } + return $ids === $foundIds; + }) + ); + break; + case 'delete': + $this->connectionMock + ->expects($this->exactly(empty($ids) ? 0 : 1)) + ->method('delete') + // Verify that the correct category ID's are touched: + ->with( + $this->anything(), + $this->callback(function ($data) use ($ids) { + return array_values($data)[1] === $ids; + }) + ); + break; + } + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index dbbb3fb29513b..bb39aa7f9db77 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -58,13 +58,18 @@ class CollectionTest extends \PHPUnit\Framework\TestCase */ private $storeManager; + /** + * @var \Magento\Framework\Data\Collection\EntityFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $entityFactory; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class); + $this->entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class); $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -168,7 +173,7 @@ protected function setUp() $this->collection = $this->objectManager->getObject( \Magento\Catalog\Model\ResourceModel\Product\Collection::class, [ - 'entityFactory' => $entityFactory, + 'entityFactory' => $this->entityFactory, 'logger' => $logger, 'fetchStrategy' => $fetchStrategy, 'eventManager' => $eventManager, @@ -379,4 +384,20 @@ public function testAddTierPriceData() $this->assertSame($this->collection, $this->collection->addTierPriceData()); } + + /** + * Test for getNewEmptyItem() method + * + * @return void + */ + public function testGetNewEmptyItem() + { + $item = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityFactory->expects($this->once())->method('create')->willReturn($item); + $firstItem = $this->collection->getNewEmptyItem(); + $secondItem = $this->collection->getNewEmptyItem(); + $this->assertEquals($firstItem, $secondItem); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php index dbf1292e57368..a4ccaffc8fb6a 100644 --- a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php @@ -152,21 +152,21 @@ public function productJsonEncodeDataProvider() : array return [ [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test ™']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test \u2122"}}', + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test \u2122"}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test "']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test ""}}', + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test ""}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test <b>x</b>']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":' + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":' . '"Test <b>x<\/b>"}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test \'abc\'']]), '{"breadcrumbs":' - . '{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test 'abc'"}}' + . '{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test 'abc'"}}' ], ]; } diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php index cbc67fee8a5a3..1903bcd144831 100644 --- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php @@ -6,6 +6,8 @@ namespace Magento\Catalog\Ui\Component; /** + * Column Factory + * * @api * @since 100.0.2 */ @@ -47,10 +49,14 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $ } /** + * Create Factory + * * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context * @param array $config + * * @return \Magento\Ui\Component\Listing\Columns\ColumnInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function create($attribute, $context, array $config = []) { @@ -82,7 +88,10 @@ public function create($attribute, $context, array $config = []) } /** + * Get Js Component + * * @param string $dataType + * * @return string */ protected function getJsComponent($dataType) @@ -91,14 +100,15 @@ protected function getJsComponent($dataType) } /** + * Get Data Type + * * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * * @return string */ protected function getDataType($attribute) { - return isset($this->dataTypeMap[$attribute->getFrontendInput()]) - ? $this->dataTypeMap[$attribute->getFrontendInput()] - : $this->dataTypeMap['default']; + return $this->dataTypeMap[$attribute->getFrontendInput()] ?? $this->dataTypeMap['default']; } /** @@ -111,6 +121,6 @@ protected function getFilterType($frontendInput) { $filtersMap = ['date' => 'dateRange']; $result = array_replace_recursive($this->dataTypeMap, $filtersMap); - return isset($result[$frontendInput]) ? $result[$frontendInput] : $result['default']; + return $result[$frontendInput] ?? $result['default']; } } diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php index d4dc9ddd7ca3b..09c9782fc0e32 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php @@ -9,6 +9,8 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; /** + * Class Thumbnail + * * @api * @since 100.0.2 */ @@ -67,6 +69,8 @@ public function prepareDataSource(array $dataSource) } /** + * Get Alt + * * @param array $row * * @return null|string @@ -74,6 +78,6 @@ public function prepareDataSource(array $dataSource) protected function getAlt($row) { $altField = $this->getData('config/altField') ?: self::ALT_FIELD; - return isset($row[$altField]) ? $row[$altField] : null; + return $row[$altField] ?? null; } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php index 524927ac1c4b4..4fcb87ab1396e 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php @@ -14,6 +14,7 @@ use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException; use Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorInterface; use Magento\Framework\App\State; +use Magento\Framework\View\Design\ThemeInterface; use Magento\Framework\View\DesignInterface; use Magento\Store\Model\StoreManager; use Magento\Store\Model\StoreManagerInterface; @@ -104,6 +105,8 @@ public function __construct( public function collect(ProductInterface $product, ProductRenderInterface $productRender) { $images = []; + /** @var ThemeInterface $currentTheme */ + $currentTheme = $this->design->getDesignTheme(); foreach ($this->imageCodes as $imageCode) { /** @var ImageInterface $image */ @@ -132,6 +135,7 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ $images[] = $image; } + $this->design->setDesignTheme($currentTheme); $productRender->setImages($images); } diff --git a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php index 95f2531e5fdca..d1424d637937b 100644 --- a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php +++ b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php @@ -105,7 +105,7 @@ public function getJsonConfigurationHtmlEscaped() : string [ 'breadcrumbs' => [ 'categoryUrlSuffix' => $this->escaper->escapeHtml($this->getCategoryUrlSuffix()), - 'userCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(), + 'useCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(), 'product' => $this->escaper->escapeHtml($this->getProductName()) ] ], diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml index 4a27158be5f7c..d7aa058a1f446 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since cross-sell products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Crosssell" name="catalog.product.edit.tab.crosssell"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml index b5efecf0d03c2..3ba4562c9d3df 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since cross-sell products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Crosssell" name="catalog.product.edit.tab.crosssell"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml index 1340d40ef4f9f..c40c4a2818efa 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since related products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Related" name="catalog.product.edit.tab.related"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml index 1ae83419ae646..38b791d88a00d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since related products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Related" name="catalog.product.edit.tab.related"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml index a9770ed3c182e..eea4450411945 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since upsell products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Upsell" name="catalog.product.edit.tab.upsell"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml index a4acf572caeb5..2c400746c64f2 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Not used since upsell products grid moved to UI components. +@see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Upsell" name="catalog.product.edit.tab.upsell"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml index 9865589556e7b..93666470b1b2c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml @@ -248,7 +248,7 @@ 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 (!response || !response['parameters']) { diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml index 07a801f42a786..195ac92422715 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -334,7 +334,7 @@ if ($('is_required')) { jQuery(function($) { bindAttributeInputType(); - // @todo: refactor collapsable component + // @todo: refactor collapsible component $('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]') .collapsable() .collapse('hide'); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml index 670a943da0aad..9621b9a57168c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml @@ -80,7 +80,7 @@ // set the root node this.root = new Ext.tree.TreeNode({ text: 'ROOT', - allowDrug:false, + allowDrag:false, allowDrop:true, id:'1' }); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js index a2804a8723ce0..1ac2a4ffadaae 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js @@ -91,8 +91,8 @@ define([ /** * Add product list types as scope and their urls - * expamle: addListType('product_to_add', {urlFetch: 'http://magento...'}) - * expamle: addListType('wishlist', {urlSubmit: 'http://magento...'}) + * example: addListType('product_to_add', {urlFetch: 'http://magento...'}) + * example: addListType('wishlist', {urlSubmit: 'http://magento...'}) * * @param type types as scope * @param urls obj can be @@ -112,7 +112,7 @@ define([ /** * Adds complex list type - that is used to submit several list types at once * Only urlSubmit is possible for this list type - * expamle: addComplexListType(['wishlist', 'product_list'], 'http://magento...') + * example: addComplexListType(['wishlist', 'product_list'], 'http://magento...') * * @param type types as scope * @param urls obj can be diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js index 97f978de47b60..f829c66c4011c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js @@ -5,10 +5,10 @@ define([ 'underscore', 'Magento_Ui/js/form/element/abstract' -], function (_, Acstract) { +], function (_, Abstract) { 'use strict'; - return Acstract.extend({ + return Abstract.extend({ defaults: { prefixName: '', prefixElementName: '', diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js index 2f6703cc92eac..4bbdea066b762 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js @@ -5,10 +5,10 @@ define([ 'underscore', 'Magento_Ui/js/form/element/abstract' -], function (_, Acstract) { +], function (_, Abstract) { 'use strict'; - return Acstract.extend({ + return Abstract.extend({ defaults: { prefixName: '', prefixElementName: '', diff --git a/app/code/Magento/Catalog/view/base/web/js/price-utils.js b/app/code/Magento/Catalog/view/base/web/js/price-utils.js index e2ea42f7d5fe3..7b83d12cc9804 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-utils.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-utils.js @@ -60,7 +60,7 @@ define([ pattern = pattern.indexOf('{sign}') < 0 ? s + pattern : pattern.replace('{sign}', s); // we're avoiding the usage of to fixed, and using round instead with the e representation to address - // numbers like 1.005 = 1.01. Using ToFixed to only provide trailig zeroes in case we have a whole number + // numbers like 1.005 = 1.01. Using ToFixed to only provide trailing zeroes in case we have a whole number i = parseInt( amount = Number(Math.round(Math.abs(+amount || 0) + 'e+' + precision) + ('e-' + precision)), 10 diff --git a/app/code/Magento/CatalogAnalytics/README.md b/app/code/Magento/CatalogAnalytics/README.md index df125446117a3..f93b223c342d7 100644 --- a/app/code/Magento/CatalogAnalytics/README.md +++ b/app/code/Magento/CatalogAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_CatalogAnalytics module -The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index ff53299b00e33..e910a5c8be4cd 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -7,7 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; -use Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters; +use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; @@ -17,6 +17,7 @@ use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\SearchFilter; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Api\Search\SearchCriteriaInterface; /** * Products field resolver, used for GraphQL request processing. @@ -81,10 +82,10 @@ public function resolve( } elseif (isset($args['search'])) { $layerType = Resolver::CATALOG_LAYER_SEARCH; $this->searchFilter->add($args['search'], $searchCriteria); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); + $searchResult = $this->getSearchResult($this->searchQuery, $searchCriteria, $info); } else { $layerType = Resolver::CATALOG_LAYER_CATEGORY; - $searchResult = $this->filterQuery->getResult($searchCriteria, $info); + $searchResult = $this->getSearchResult($this->filterQuery, $searchCriteria, $info); } //possible division by 0 if ($searchCriteria->getPageSize()) { @@ -116,4 +117,25 @@ public function resolve( return $data; } + + /** + * Get search result. + * + * @param Filter|Search $query + * @param SearchCriteriaInterface $searchCriteria + * @param ResolveInfo $info + * + * @return \Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult + * @throws GraphQlInputException + */ + private function getSearchResult($query, SearchCriteriaInterface $searchCriteria, ResolveInfo $info) + { + try { + $searchResult = $query->getResult($searchCriteria, $info); + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + return $searchResult; + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 345d323e6e129..c047e9c9bef21 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -1158,7 +1158,7 @@ protected function collectMultiselectValues($item, $attrCode, $storeId) } /** - * Check attribute is valid + * Check attribute is valid. * * @param string $code * @param mixed $value @@ -1175,6 +1175,10 @@ protected function isValidAttributeValue($code, $value) $isValid = false; } + if (is_array($value)) { + $isValid = false; + } + return $isValid; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php index 7e6ada724a81a..3ac7f98818d70 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php @@ -180,9 +180,9 @@ public function move($fileName, $renameFileOff = false) } $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $fileName); - $filePath = $this->_directory->getRelativePath($filePath . $fileName); + $relativePath = $this->_directory->getRelativePath($filePath . $fileName); $this->_directory->writeFile( - $filePath, + $relativePath, $read->readAll() ); } diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php index 5e7210c1b7444..f9a49d4f8d121 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php @@ -9,11 +9,11 @@ use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\ResourceModel\Stock\Item; -use Magento\CatalogInventory\Model\Stock; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceModifierInterface; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Query\Generator; /** * Class for filter product price index. @@ -40,22 +40,38 @@ class ProductPriceIndexFilter implements PriceModifierInterface */ private $connectionName; + /** + * @var Generator + */ + private $batchQueryGenerator; + + /** + * @var int + */ + private $batchSize; + /** * @param StockConfigurationInterface $stockConfiguration * @param Item $stockItem * @param ResourceConnection $resourceConnection * @param string $connectionName + * @param Generator $batchQueryGenerator + * @param int $batchSize */ public function __construct( StockConfigurationInterface $stockConfiguration, Item $stockItem, ResourceConnection $resourceConnection = null, - $connectionName = 'indexer' + $connectionName = 'indexer', + Generator $batchQueryGenerator = null, + $batchSize = 100 ) { $this->stockConfiguration = $stockConfiguration; $this->stockItem = $stockItem; $this->resourceConnection = $resourceConnection ?: ObjectManager::getInstance()->get(ResourceConnection::class); $this->connectionName = $connectionName; + $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(Generator::class); + $this->batchSize = $batchSize; } /** @@ -76,32 +92,37 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = $connection = $this->resourceConnection->getConnection($this->connectionName); $select = $connection->select(); + $select->from( - ['price_index' => $priceTable->getTableName()], - [] - ); - $select->joinInner( ['stock_item' => $this->stockItem->getMainTable()], - 'stock_item.product_id = price_index.' . $priceTable->getEntityField() - . ' AND stock_item.stock_id = ' . Stock::DEFAULT_STOCK_ID, - [] + ['stock_item.product_id', 'MAX(stock_item.is_in_stock) as max_is_in_stock'] ); + if ($this->stockConfiguration->getManageStock()) { - $stockStatus = $connection->getCheckSql( - 'use_config_manage_stock = 0 AND manage_stock = 0', - Stock::STOCK_IN_STOCK, - 'is_in_stock' - ); + $select->where('stock_item.use_config_manage_stock = 1 OR stock_item.manage_stock = 1'); } else { - $stockStatus = $connection->getCheckSql( - 'use_config_manage_stock = 0 AND manage_stock = 1', - 'is_in_stock', - Stock::STOCK_IN_STOCK - ); + $select->where('stock_item.use_config_manage_stock = 0 AND stock_item.manage_stock = 1'); } - $select->where($stockStatus . ' = ?', Stock::STOCK_OUT_OF_STOCK); - $query = $select->deleteFromSelect('price_index'); - $connection->query($query); + $select->group('stock_item.product_id'); + $select->having('max_is_in_stock = 0'); + + $batchSelectIterator = $this->batchQueryGenerator->generate( + 'product_id', + $select, + $this->batchSize, + \Magento\Framework\DB\Query\BatchIteratorInterface::UNIQUE_FIELD_ITERATOR + ); + + foreach ($batchSelectIterator as $select) { + $productIds = null; + foreach ($connection->query($select)->fetchAll() as $row) { + $productIds[] = $row['product_id']; + } + if ($productIds !== null) { + $where = [$priceTable->getEntityField() .' IN (?)' => $productIds]; + $connection->delete($priceTable->getTableName(), $where); + } + } } } diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml index 27e8d1e999907..706df79b1ef8b 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml @@ -104,6 +104,8 @@ <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <waitForPageLoad stepKey="waitForOrderSuccessPage1"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> diff --git a/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php b/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php index 5d93e6f216866..6b7c12dfdf463 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php @@ -10,6 +10,9 @@ use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Indexer\CacheContext; +/** + * Abstract class for CatalogRule indexers. + */ abstract class AbstractIndexer implements IndexerActionInterface, MviewActionInterface, IdentityInterface { /** @@ -66,7 +69,6 @@ public function executeFull() { $this->indexBuilder->reindexFull(); $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); - //TODO: remove after fix fpc. MAGETWO-50668 $this->getCacheManager()->clean($this->getIdentities()); } @@ -137,8 +139,9 @@ public function executeRow($id) abstract protected function doExecuteRow($id); /** - * @return \Magento\Framework\App\CacheInterface|mixed + * Get cache manager * + * @return \Magento\Framework\App\CacheInterface|mixed * @deprecated 100.0.7 */ private function getCacheManager() diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml index 2e09ee7134733..cac7c94de446f 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-69455"/> <group value="persistent"/> + <skip> + <issueId value="MC-5777"/> + </skip> </annotations> <before> <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> diff --git a/app/code/Magento/CatalogRule/etc/db_schema.xml b/app/code/Magento/CatalogRule/etc/db_schema.xml index 6f7d713e49fe3..894f057ba73d1 100644 --- a/app/code/Magento/CatalogRule/etc/db_schema.xml +++ b/app/code/Magento/CatalogRule/etc/db_schema.xml @@ -23,7 +23,7 @@ <column xsi:type="int" name="sort_order" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Sort Order"/> <column xsi:type="varchar" name="simple_action" nullable="true" length="32" comment="Simple Action"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="rule_id"/> @@ -49,7 +49,7 @@ default="0" comment="Product Id"/> <column xsi:type="varchar" name="action_operator" nullable="true" length="10" default="to_fixed" comment="Action Operator"/> - <column xsi:type="decimal" name="action_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="action_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Action Amount"/> <column xsi:type="smallint" name="action_stop" padding="6" unsigned="false" nullable="false" identity="false" default="0" comment="Action Stop"/> diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php index 17d70e38d3b61..184fd9cfd5b37 100644 --- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php +++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php @@ -17,6 +17,11 @@ */ class Result extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface { + /** + * No results default handle. + */ + const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_advanced_result_noresults'; + /** * Url factory * @@ -55,7 +60,16 @@ public function execute() { try { $this->_catalogSearchAdvanced->addFilters($this->getRequest()->getQueryValue()); - $this->_view->loadLayout(); + $size = $this->_catalogSearchAdvanced->getProductCollection()->getSize(); + + $handles = null; + if ($size == 0) { + $this->_view->getPage()->initLayout(); + $handles = $this->_view->getLayout()->getUpdate()->getHandles(); + $handles[] = static::DEFAULT_NO_RESULT_HANDLE; + } + + $this->_view->loadLayout($handles); $this->_view->renderLayout(); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); diff --git a/app/code/Magento/CatalogSearch/Controller/Result/Index.php b/app/code/Magento/CatalogSearch/Controller/Result/Index.php index fba4572cf2c10..975c6ba1e7eb9 100644 --- a/app/code/Magento/CatalogSearch/Controller/Result/Index.php +++ b/app/code/Magento/CatalogSearch/Controller/Result/Index.php @@ -20,6 +20,11 @@ */ class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface { + /** + * No results default handle. + */ + const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_result_index_noresults'; + /** * Catalog session * @@ -90,12 +95,19 @@ public function execute() $getAdditionalRequestParameters = $this->getRequest()->getParams(); unset($getAdditionalRequestParameters[QueryFactory::QUERY_VAR_NAME]); + $handles = null; + if ($query->getNumResults() == 0) { + $this->_view->getPage()->initLayout(); + $handles = $this->_view->getLayout()->getUpdate()->getHandles(); + $handles[] = static::DEFAULT_NO_RESULT_HANDLE; + } + if (empty($getAdditionalRequestParameters) && $this->_objectManager->get(PopularSearchTerms::class)->isCacheable($queryText, $storeId) ) { - $this->getCacheableResult($catalogSearchHelper, $query); + $this->getCacheableResult($catalogSearchHelper, $query, $handles); } else { - $this->getNotCacheableResult($catalogSearchHelper, $query); + $this->getNotCacheableResult($catalogSearchHelper, $query, $handles); } } else { $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); @@ -107,9 +119,10 @@ public function execute() * * @param \Magento\CatalogSearch\Helper\Data $catalogSearchHelper * @param \Magento\Search\Model\Query $query + * @param array $handles * @return void */ - private function getCacheableResult($catalogSearchHelper, $query) + private function getCacheableResult($catalogSearchHelper, $query, $handles) { if (!$catalogSearchHelper->isMinQueryLength()) { $redirect = $query->getRedirect(); @@ -121,7 +134,7 @@ private function getCacheableResult($catalogSearchHelper, $query) $catalogSearchHelper->checkNotes(); - $this->_view->loadLayout(); + $this->_view->loadLayout($handles); $this->_view->renderLayout(); } @@ -130,11 +143,12 @@ private function getCacheableResult($catalogSearchHelper, $query) * * @param \Magento\CatalogSearch\Helper\Data $catalogSearchHelper * @param \Magento\Search\Model\Query $query + * @param array $handles * @return void * * @throws \Magento\Framework\Exception\LocalizedException */ - private function getNotCacheableResult($catalogSearchHelper, $query) + private function getNotCacheableResult($catalogSearchHelper, $query, $handles) { if ($catalogSearchHelper->isMinQueryLength()) { $query->setId(0)->setIsActive(1)->setIsProcessed(1); @@ -149,7 +163,7 @@ private function getNotCacheableResult($catalogSearchHelper, $query) $catalogSearchHelper->checkNotes(); - $this->_view->loadLayout(); + $this->_view->loadLayout($handles); $this->getResponse()->setNoCacheHeaders(); $this->_view->renderLayout(); } diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php index 7aac6e98fc044..7b239d84bf962 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php @@ -91,12 +91,10 @@ protected function _getItemsData() return $this->itemDataBuilder->build(); } - $productSize = $productCollection->getSize(); - $options = $attribute->getFrontend() ->getSelectOptions(); foreach ($options as $option) { - $this->buildOptionData($option, $isAttributeFilterable, $optionsFacetedData, $productSize); + $this->buildOptionData($option, $isAttributeFilterable, $optionsFacetedData); } return $this->itemDataBuilder->build(); @@ -108,17 +106,16 @@ protected function _getItemsData() * @param array $option * @param boolean $isAttributeFilterable * @param array $optionsFacetedData - * @param int $productSize * @return void */ - private function buildOptionData($option, $isAttributeFilterable, $optionsFacetedData, $productSize) + private function buildOptionData($option, $isAttributeFilterable, $optionsFacetedData) { $value = $this->getOptionValue($option); if ($value === false) { return; } $count = $this->getOptionCount($value, $optionsFacetedData); - if ($isAttributeFilterable && (!$this->isOptionReducesResults($count, $productSize) || $count === 0)) { + if ($isAttributeFilterable && $count === 0) { return; } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php index 49caede8c4ac2..93ae2c94e2105 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php @@ -110,7 +110,9 @@ public function processAttributeValue($attribute, $value) && in_array($attribute->getFrontendInput(), ['text', 'textarea']) ) { $result = $value; - } elseif ($this->isTermFilterableAttribute($attribute)) { + } elseif ($this->isTermFilterableAttribute($attribute) + || ($attribute->getIsSearchable() && in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) + ) { $result = ''; } @@ -119,6 +121,7 @@ public function processAttributeValue($attribute, $value) /** * Prepare index array as a string glued by separator + * * Support 2 level array gluing * * @param array $index diff --git a/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php b/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php index c122bae15cb0c..a47ea54375205 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php +++ b/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php @@ -25,7 +25,7 @@ class FullTextSearchCheck * to join catalog_eav_attribute table to search query or not * * In case when the $query does not requires full text search - * - we can skipp joining catalog_eav_attribute table because it becomes excessive + * - we can skip joining catalog_eav_attribute table because it becomes excessive * * @param QueryInterface $query * @return bool @@ -37,6 +37,8 @@ public function isRequiredForQuery(QueryInterface $query) } /** + * Process query + * * @param QueryInterface $query * @return bool * @throws \InvalidArgumentException @@ -62,6 +64,8 @@ private function processQuery(QueryInterface $query) } /** + * Process boolean query + * * @param BoolExpression $query * @return bool * @throws \InvalidArgumentException @@ -90,6 +94,8 @@ private function processBoolQuery(BoolExpression $query) } /** + * Process filter query + * * @param Filter $query * @return bool * @throws \InvalidArgumentException diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php index 891f008979e17..2faacea24262c 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php @@ -10,6 +10,11 @@ */ class ResultTest extends \PHPUnit\Framework\TestCase { + /** + * Test result action filters set before load layout scenario + * + * @return void + */ public function testResultActionFiltersSetBeforeLoadLayout() { $filters = null; @@ -27,9 +32,15 @@ function () use (&$filters, $expectedQuery) { $request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']); $request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery)); + $collection = $this->createPartialMock( + \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, + ['getSize'] + ); + $collection->expects($this->once())->method('getSize')->will($this->returnValue(1)); + $catalogSearchAdvanced = $this->createPartialMock( \Magento\CatalogSearch\Model\Advanced::class, - ['addFilters', '__wakeup'] + ['addFilters', '__wakeup', 'getProductCollection'] ); $catalogSearchAdvanced->expects($this->once())->method('addFilters')->will( $this->returnCallback( @@ -38,6 +49,8 @@ function ($added) use (&$filters) { } ) ); + $catalogSearchAdvanced->expects($this->once())->method('getProductCollection') + ->will($this->returnValue($collection)); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $context = $objectManager->getObject( @@ -53,6 +66,11 @@ function ($added) use (&$filters) { $instance->execute(); } + /** + * Test url set on exception scenario + * + * @return void + */ public function testUrlSetOnException() { $redirectResultMock = $this->createMock(\Magento\Framework\Controller\Result\Redirect::class); @@ -131,11 +149,71 @@ public function testUrlSetOnException() /** @var \Magento\CatalogSearch\Controller\Advanced\Result $instance */ $instance = $objectManager->getObject( \Magento\CatalogSearch\Controller\Advanced\Result::class, - ['context' => $contextMock, - 'catalogSearchAdvanced' => $catalogSearchAdvanced, - 'urlFactory' => $urlFactoryMock + [ + 'context' => $contextMock, + 'catalogSearchAdvanced' => $catalogSearchAdvanced, + 'urlFactory' => $urlFactoryMock ] ); $this->assertEquals($redirectResultMock, $instance->execute()); } + + /** + * Test no result handle scenario + * + * @return void + */ + public function testNoResultsHandle() + { + $expectedQuery = 'notExistTerm'; + + $update = $this->createPartialMock(\Magento\Framework\View\Model\Layout\Merge::class, ['getHandles']); + $update->expects($this->once())->method('getHandles')->will($this->returnValue([])); + + $layout = $this->createPartialMock(\Magento\Framework\View\Result\Layout::class, ['getUpdate']); + $layout->expects($this->once())->method('getUpdate')->will($this->returnValue($update)); + + $page = $this->createPartialMock(\Magento\Framework\View\Result\Page::class, ['initLayout']); + + $view = $this->createPartialMock( + \Magento\Framework\App\View::class, + ['loadLayout', 'renderLayout', 'getPage', 'getLayout'] + ); + + $view->expects($this->once())->method('loadLayout') + ->with([\Magento\CatalogSearch\Controller\Advanced\Result::DEFAULT_NO_RESULT_HANDLE]); + + $view->expects($this->once())->method('getPage')->will($this->returnValue($page)); + $view->expects($this->once())->method('getLayout')->will($this->returnValue($layout)); + + $request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']); + $request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery)); + + $collection = $this->createPartialMock( + \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, + ['getSize'] + ); + $collection->expects($this->once())->method('getSize')->will($this->returnValue(0)); + + $catalogSearchAdvanced = $this->createPartialMock( + \Magento\CatalogSearch\Model\Advanced::class, + ['addFilters', '__wakeup', 'getProductCollection'] + ); + + $catalogSearchAdvanced->expects($this->once())->method('getProductCollection') + ->will($this->returnValue($collection)); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $context = $objectManager->getObject( + \Magento\Framework\App\Action\Context::class, + ['view' => $view, 'request' => $request] + ); + + /** @var \Magento\CatalogSearch\Controller\Advanced\Result $instance */ + $instance = $objectManager->getObject( + \Magento\CatalogSearch\Controller\Advanced\Result::class, + ['context' => $context, 'catalogSearchAdvanced' => $catalogSearchAdvanced] + ); + $instance->execute(); + } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php index abc0fdd1069fe..69e2c33d02d1a 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php @@ -321,10 +321,6 @@ public function testGetItemsWithoutApply() ->method('build') ->will($this->returnValue($builtData)); - $this->fulltextCollection->expects($this->once()) - ->method('getSize') - ->will($this->returnValue(50)); - $expectedFilterItems = [ $this->createFilterItem(0, $builtData[0]['label'], $builtData[0]['value'], $builtData[0]['count']), $this->createFilterItem(1, $builtData[1]['label'], $builtData[1]['value'], $builtData[1]['count']), @@ -383,9 +379,6 @@ public function testGetItemsOnlyWithResults() $this->fulltextCollection->expects($this->once()) ->method('getFacetedData') ->willReturn($facetedData); - $this->fulltextCollection->expects($this->once()) - ->method('getSize') - ->will($this->returnValue(50)); $this->itemDataBuilder->expects($this->once()) ->method('addItemData') diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php b/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php index 019959f9c1fea..a048c216139e3 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php @@ -5,6 +5,9 @@ */ namespace Magento\CatalogUrlRewrite\Model; +/** + * Class ObjectRegistry + */ class ObjectRegistry { /** @@ -26,15 +29,19 @@ public function __construct($entities) } /** + * Get Entity + * * @param int $entityId * @return \Magento\Framework\DataObject|null */ public function get($entityId) { - return isset($this->entitiesMap[$entityId]) ? $this->entitiesMap[$entityId] : null; + return $this->entitiesMap[$entityId] ?? null; } /** + * List Entities + * * @return \Magento\Framework\DataObject[] */ public function getList() diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php index 5d7e323e8b2d8..3cfd49b1d210a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php @@ -100,16 +100,19 @@ public function execute(\Magento\Framework\Event\Observer $observer) } $mapsGenerated = false; - if ($category->dataHasChangedFor('url_key') - || $category->dataHasChangedFor('is_anchor') - || !empty($category->getChangedProductIds()) - ) { + if ($this->isCategoryHasChanged($category)) { if ($category->dataHasChangedFor('url_key')) { $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category); $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult); } - $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); - $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + if ($this->isChangedOnlyProduct($category)) { + $productUrlRewriteResult = + $this->urlRewriteHandler->updateProductUrlRewritesForChangedProduct($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } else { + $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } $mapsGenerated = true; } @@ -119,6 +122,38 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } + /** + * Check is category changed changed. + * + * @param Category $category + * @return bool + */ + private function isCategoryHasChanged(Category $category): bool + { + if ($category->dataHasChangedFor('url_key') + || $category->dataHasChangedFor('is_anchor') + || !empty($category->getChangedProductIds())) { + return true; + } + return false; + } + + /** + * Check is only product changed. + * + * @param Category $category + * @return bool + */ + private function isChangedOnlyProduct(Category $category): bool + { + if (!empty($category->getChangedProductIds()) + && !$category->dataHasChangedFor('is_anchor') + && !$category->dataHasChangedFor('url_key')) { + return true; + } + return false; + } + /** * In case store_id is not set for category then we can assume that it was passed through product import. * Store group must have only one root category, so receiving category's path and checking if one of it parts diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index c4ec0bb3a74b2..b4a35f323e1bc 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -24,6 +24,8 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; /** + * Class for management url rewrites. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class UrlRewriteHandler @@ -156,6 +158,30 @@ public function generateProductUrlRewrites(Category $category): array } /** + * Update product url rewrites for changed product. + * + * @param Category $category + * @return array + */ + public function updateProductUrlRewritesForChangedProduct(Category $category): array + { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $this->isSkippedProduct[$category->getEntityId()] = []; + $saveRewriteHistory = (bool)$category->getData('save_rewrites_history'); + $storeIds = $this->getCategoryStoreIds($category); + + if ($category->getChangedProductIds()) { + foreach ($storeIds as $storeId) { + $this->generateChangedProductUrls($mergeDataProvider, $category, (int)$storeId, $saveRewriteHistory); + } + } + + return $mergeDataProvider->getData(); + } + + /** + * Delete category rewrites for children. + * * @param Category $category * @return void */ @@ -184,6 +210,8 @@ public function deleteCategoryRewritesForChildren(Category $category) } /** + * Get category products url rewrites. + * * @param Category $category * @param int $storeId * @param bool $saveRewriteHistory diff --git a/app/code/Magento/Checkout/Block/Onepage.php b/app/code/Magento/Checkout/Block/Onepage.php index ca6b045ddbb5d..e01d5835b4cf0 100644 --- a/app/code/Magento/Checkout/Block/Onepage.php +++ b/app/code/Magento/Checkout/Block/Onepage.php @@ -38,7 +38,7 @@ class Onepage extends \Magento\Framework\View\Element\Template protected $layoutProcessors; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var \Magento\Framework\Serialize\SerializerInterface */ private $serializer; @@ -48,8 +48,9 @@ class Onepage extends \Magento\Framework\View\Element\Template * @param \Magento\Checkout\Model\CompositeConfigProvider $configProvider * @param array $layoutProcessors * @param array $data - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer - * @throws \RuntimeException + * @param \Magento\Framework\Serialize\Serializer\Json $serializer + * @param \Magento\Framework\Serialize\SerializerInterface $serializerInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -57,7 +58,8 @@ public function __construct( \Magento\Checkout\Model\CompositeConfigProvider $configProvider, array $layoutProcessors = [], array $data = [], - \Magento\Framework\Serialize\Serializer\Json $serializer = null + \Magento\Framework\Serialize\Serializer\Json $serializer = null, + \Magento\Framework\Serialize\SerializerInterface $serializerInterface = null ) { parent::__construct($context, $data); $this->formKey = $formKey; @@ -65,12 +67,12 @@ public function __construct( $this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ? $data['jsLayout'] : []; $this->configProvider = $configProvider; $this->layoutProcessors = $layoutProcessors; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = $serializerInterface ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); } /** - * @return string + * @inheritdoc */ public function getJsLayout() { @@ -78,7 +80,7 @@ public function getJsLayout() $this->jsLayout = $processor->process($this->jsLayout); } - return json_encode($this->jsLayout, JSON_HEX_TAG); + return $this->serializer->serialize($this->jsLayout); } /** @@ -115,11 +117,13 @@ public function getBaseUrl() } /** + * Retrieve serialized checkout config. + * * @return bool|string * @since 100.2.0 */ public function getSerializedCheckoutConfig() { - return json_encode($this->getCheckoutConfig(), JSON_HEX_TAG); + return $this->serializer->serialize($this->getCheckoutConfig()); } } diff --git a/app/code/Magento/Checkout/CustomerData/Cart.php b/app/code/Magento/Checkout/CustomerData/Cart.php index ddb077462ef10..01e91d75c02d9 100644 --- a/app/code/Magento/Checkout/CustomerData/Cart.php +++ b/app/code/Magento/Checkout/CustomerData/Cart.php @@ -82,7 +82,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getSectionData() { @@ -158,11 +158,10 @@ protected function getRecentItems() : $item->getProduct(); $products = $this->catalogUrl->getRewriteByProductStore([$product->getId() => $item->getStoreId()]); - if (!isset($products[$product->getId()])) { - continue; + if (isset($products[$product->getId()])) { + $urlDataObject = new \Magento\Framework\DataObject($products[$product->getId()]); + $item->getProduct()->setUrlDataObject($urlDataObject); } - $urlDataObject = new \Magento\Framework\DataObject($products[$product->getId()]); - $item->getProduct()->setUrlDataObject($urlDataObject); } $items[] = $this->itemPoolInterface->getItemData($item); } diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index fd120f0a37a4b..f30bd73deeae2 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -282,10 +282,12 @@ public function getConfig() $quote = $this->checkoutSession->getQuote(); $quoteId = $quote->getId(); $email = $quote->getShippingAddress()->getEmail(); + $quoteItemData = $this->getQuoteItemData(); $output['formKey'] = $this->formKey->getFormKey(); $output['customerData'] = $this->getCustomerData(); $output['quoteData'] = $this->getQuoteData(); - $output['quoteItemData'] = $this->getQuoteItemData(); + $output['quoteItemData'] = $quoteItemData; + $output['quoteMessages'] = $this->getQuoteItemsMessages($quoteItemData); $output['isCustomerLoggedIn'] = $this->isCustomerLoggedIn(); $output['selectedShippingMethod'] = $this->getSelectedShippingMethod(); if ($email && !$this->isCustomerLoggedIn()) { @@ -316,6 +318,7 @@ public function getConfig() ); $output['postCodes'] = $this->postCodesConfig->getPostCodes(); $output['imageData'] = $this->imageProvider->getImages($quoteId); + $output['totalsData'] = $this->getTotalsData(); $output['shippingPolicy'] = [ 'isEnabled' => $this->scopeConfig->isSetFlag( @@ -450,6 +453,7 @@ private function getQuoteItemData() $quoteItem->getProduct(), 'product_thumbnail_image' )->getUrl(); + $quoteItemData[$index]['message'] = $quoteItem->getMessage(); } } return $quoteItemData; @@ -776,4 +780,22 @@ private function getAttributeLabels(array $customAttribute, string $customAttrib return $attributeOptionLabels; } + + /** + * Get notification messages for the quote items + * + * @param array $quoteItemData + * @return array + */ + private function getQuoteItemsMessages(array $quoteItemData): array + { + $quoteItemsMessages = []; + if ($quoteItemData) { + foreach ($quoteItemData as $item) { + $quoteItemsMessages[$item['item_id']] = $item['message']; + } + } + + return $quoteItemsMessages; + } } diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml index 18bf70a613b50..464ccc1913335 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml @@ -21,11 +21,22 @@ <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> </actionGroup> + <!-- Go to checkout from cart --> + <actionGroup name="GoToCheckoutFromCartActionGroup"> + <waitForElementNotVisible selector="{{StorefrontMinicartSection.emptyCart}}" stepKey="waitUpdateQuantity" /> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <seeInCurrentUrl url="{{CheckoutCartPage.url}}" stepKey="assertCheckoutCartUrl"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + </actionGroup> + <!-- Guest checkout filling shipping section --> <actionGroup name="GuestCheckoutFillingShippingSectionActionGroup"> <arguments> <argument name="customerVar" defaultValue="CustomerEntityOne"/> <argument name="customerAddressVar" defaultValue="CustomerAddressSimple"/> + <!--First available shipping method will be selected if value is not passed for shippingMethod--> + <argument name="shippingMethod" defaultValue="" type="string"/> </arguments> <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{customerVar.email}}" stepKey="enterEmail"/> <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> @@ -36,13 +47,18 @@ <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/> <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> - <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('shippingMethod')}}" stepKey="selectShippingMethod"/> <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> </actionGroup> + <actionGroup name="GuestCheckoutFillShippingNoWaitForPaymentActionGroup" extends="GuestCheckoutFillingShippingSectionActionGroup"> + <remove keyForRemoval="waitForPaymentSectionLoaded"/> + <remove keyForRemoval="assertCheckoutPaymentUrl"/> + </actionGroup> + <!-- Guest checkout filling shipping section without region --> <actionGroup name="GuestCheckoutFillingShippingSectionWithoutRegionActionGroup"> <arguments> @@ -88,11 +104,18 @@ <see userInput="No Payment method available." stepKey="checkMessage"/> </actionGroup> + <actionGroup name="GuestCheckoutWithSpecificCountryOptionForPaymentMethodActionGroup" extends="GuestCheckoutFillingShippingSectionUnavailablePaymentActionGroup"> + <arguments> + <argument name="paymentMethod" type="string"/> + </arguments> + <remove keyForRemoval="checkMessage"/> + <dontsee selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" parametrized="true" stepKey="paymentMethodDoesNotAvailable"/> + </actionGroup> <!-- Logged in user checkout filling shipping section --> <actionGroup name="LoggedInUserCheckoutFillingShippingSectionActionGroup"> <arguments> - <argument name="customerVar"/> - <argument name="customerAddressVar"/> + <argument name="customerVar" defaultValue="CustomerEntityOne"/> + <argument name="customerAddressVar" defaultValue="CustomerAddressSimple"/> </arguments> <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{customerVar.lastname}}" stepKey="enterLastName"/> @@ -131,6 +154,24 @@ <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> </actionGroup> + <!-- Place order with logged the user --> + <actionGroup name="PlaceOrderWithLoggedUserActionGroup"> + <arguments> + <!--First available shipping method will be selected if value is not passed for shippingMethod--> + <argument name="shippingMethod" defaultValue="" type="string"/> + </arguments> + <waitForElementVisible selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="waitProceedToCheckout"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('shippingMethod')}}" stepKey="selectShippingMethod"/> + <waitForElement selector="{{CheckoutShippingSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/> + </actionGroup> + <!-- Check product in checkout cart items --> <actionGroup name="CheckProductInCheckoutCartItemsActionGroup"> <arguments> @@ -161,7 +202,7 @@ <actionGroup name="CheckTotalsSortOrderInSummarySection"> <arguments> <argument name="elementName" type="string"/> - <argument name="positionNumber" type="integer"/> + <argument name="positionNumber" type="string"/> </arguments> <see userInput="{{elementName}}" selector="{{CheckoutCartSummarySection.elementPosition(positionNumber)}}" stepKey="assertElementPosition"/> </actionGroup> @@ -236,6 +277,7 @@ <conditionalClick selector="{{CheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="openShippingDetails"/> <see selector="{{CheckoutCartSummarySection.countryParameterized('placeNumber')}}" userInput="{{country}}" stepKey="seeCountry"/> </actionGroup> + <actionGroup name="StorefrontSignOutActionGroup"> <click selector="{{StoreFrontSignOutSection.customerAccount}}" stepKey="clickCustomerButton"/> <click selector="{{StoreFrontSignOutSection.signOut}}" stepKey="clickToSignOut"/> @@ -243,4 +285,10 @@ <see userInput="You are signed out" stepKey="signOut"/> </actionGroup> + <!--Click Place Order button--> + <actionGroup name="ClickPlaceOrderActionGroup"> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml new file mode 100644 index 0000000000000..f12bf4344ab12 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FillShippingZipForm"> + <arguments> + <argument name="address"/> + </arguments> + <conditionalClick stepKey="openShippingDetails" selector="{{CheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.country}}" time="30" stepKey="waitForCountryFieldAppears"/> + <selectOption stepKey="selectCountry" selector="{{CheckoutCartSummarySection.country}}" userInput="{{address.country}}"/> + <selectOption stepKey="selectStateProvince" selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{address.state}}"/> + <fillField stepKey="fillPostCode" selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{address.postcode}}"/> + <waitForPageLoad stepKey="waitForFormUpdate"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml index 62dd6b67b78a6..34f2cfe7f7fff 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml @@ -65,4 +65,11 @@ <selectOption selector="{{classPrefix}} {{CheckoutShippingSection.country}}" userInput="" stepKey="clearFieldCounty"/> <clearField selector="{{classPrefix}} {{CheckoutShippingSection.telephone}}" stepKey="clearFieldPhoneNumber"/> </actionGroup> -</actionGroups> \ No newline at end of file + <actionGroup name="GuestCheckoutSelectPaymentAndFillNewBillingAddressActionGroup" extends="GuestCheckoutFillNewBillingAddressActionGroup"> + <arguments> + <argument name="paymentMethod" type="string"/> + </arguments> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" after="waitForLoading3" stepKey="waitForPaymentSectionLoaded"/> + <conditionalClick selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" dependentSelector="{{CheckoutPaymentSection.billingAddress}}" visible="false" parametrized="true" before="enterFirstName" stepKey="clickCheckMoneyOrderPayment"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml index 26bc6ff641a9c..dc82932ec5ca7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml @@ -13,4 +13,26 @@ <item>Bahamas</item> </array> </entity> + <entity name="DefaultCountriesWithRequiredRegions" type="countryArray"> + <array key="country"> + <item>Australia</item> + <item>Brazil</item> + <item>Canada</item> + <item>Croatia</item> + <item>Estonia</item> + <item>India</item> + <item>Latvia</item> + <item>Lithuania</item> + <item>Romania</item> + <item>Spain</item> + <item>Switzerland</item> + <item>United States</item> + <item>Australia</item> + </array> + </entity> + <entity name="CustomCountryWithRequiredRegion" type="countryArray"> + <array key="country"> + <item>United Kingdom</item> + </array> + </entity> </entities> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml index ab82d9fdd93b5..0d3c6e419cc07 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml @@ -31,5 +31,6 @@ <element name="nthBundleOptionName" type="text" selector=".product-item-details .item-options:nth-of-type({{numOption}}) dt" parameterized="true"/> <element name="productSubtotalByName" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'subtotal')]//span[@class='price']" parameterized="true"/> <element name="updateShoppingCartButton" type="button" selector="#form-validate button[type='submit'].update" timeout="30"/> + <element name="qty" type="input" selector="//input[@data-cart-item-id='{{var}}'][@title='Qty']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 3dcf399c9ad54..0206c18b819c2 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -23,7 +23,7 @@ <element name="guestRegion" type="select" selector=".billing-address-form select[name*='region_id']"/> <element name="guestPostcode" type="input" selector=".billing-address-form input[name*='postcode']"/> <element name="guestTelephone" type="input" selector=".billing-address-form input[name*='telephone']"/> - <element name="billingAddress" type="text" selector="div.billing-address-details"/> + <element name="billingAddress" type="text" selector=".payment-method._active div.billing-address-details"/> <element name="cartItems" type="text" selector="ol.minicart-items"/> <element name="cartItemsArea" type="button" selector="div.block.items-in-cart"/> <element name="cartItemsAreaActive" type="textarea" selector="div.block.items-in-cart.active" timeout="30"/> @@ -53,5 +53,6 @@ <element name="addressAction" type="button" selector="//span[text()='{{action}}']" parameterized="true"/> <element name="addressBook" type="button" selector="//a[text()='Address Book']"/> <element name="noQuotes" type="text" selector=".no-quotes-block"/> + <element name="paymentMethodByName" type="text" selector="//*[@id='checkout-payment-method-load']//*[contains(@class, 'payment-group')]//label[normalize-space(.)='{{var1}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml index 34819f641cbc9..bc65f8a2c0816 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml @@ -16,6 +16,7 @@ <element name="orderLink" type="text" selector="a[href*=order_id].order-number" timeout="30"/> <element name="orderNumberText" type="text" selector=".checkout-success > p:nth-child(1)"/> <element name="continueShoppingButton" type="button" selector=".action.primary.continue" timeout="30"/> + <element name="createAnAccount" type="button" selector="input[value='Create an Account']" timeout="30"/> <element name="printLink" type="button" selector=".print" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml index c06af54f030e7..55c4385706ba9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontCheckoutPaymentMethodSection"> <element name="billingAddress" type="text" selector=".checkout-billing-address"/> <element name="checkPaymentMethodByName" type="radio" selector="//div[@id='checkout-payment-method-load']//div[@class='payment-method']//label//span[contains(., '{{methodName}}')]/../..//input" parameterized="true"/> + <element name="billingAddressSameAsShipping" type="checkbox" selector=".payment-method._active [name='billing-address-same-as-shipping']"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml index 3f717943fe8f0..a894f2fbb1af9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -29,5 +29,6 @@ <element name="itemDiscount" type="text" selector="//tr[@class='totals']//td[@class='amount']/span"/> <element name="subtotal" type="text" selector="//tr[@class='totals sub']//td[@class='amount']/span"/> <element name="emptyCart" type="text" selector=".counter.qty.empty"/> + <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml index a41b1afc74368..e19627e7435d6 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml @@ -55,8 +55,8 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoadedTest3"/> - + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <!--Click Place Order button--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> @@ -81,7 +81,8 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton2"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext2"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoadedTest4"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> <!--Click Place Order button--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder2"/> @@ -105,7 +106,9 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton3"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext3"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoadedTest5"/> + + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment3"/> <!--Click Place Order button--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder3"/> @@ -165,6 +168,9 @@ <argument name="customerAddressVar" value="CustomerAddressSimple" /> </actionGroup> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> + <!--Click Place Order button--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/> @@ -198,6 +204,9 @@ <argument name="customerAddressVar" value="CustomerAddressSimple" /> </actionGroup> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> + <!--Click Place Order button--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder2"/> <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage2"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml new file mode 100644 index 0000000000000..4b4ca1935fd78 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CheckNotVisibleProductInMinicartTest"> + <annotations> + <features value="Checkout"/> + <stories value="MAGETWO-96422: Hidden Products are absent in Storefront Mini-Cart" /> + <title value="Not visible individually product in mini-shopping cart."/> + <description value="To be sure that product in mini-shopping cart remains visible after admin makes it not visible individually"/> + <severity value="MAJOR"/> + <group value="checkout"/> + </annotations> + + <!--Create simple product1 and simple product2--> + <createData entity="SimpleTwo" stepKey="createSimpleProduct1"/> + <createData entity="SimpleTwo" stepKey="createSimpleProduct2"/> + + <!--Go to simple product1 page--> + <amOnPage url="$$createSimpleProduct1.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage1"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad"/> + + <!--Add simple product1 to Shopping Cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + </actionGroup> + + <!--Check simple product1 in minicart--> + <comment userInput="Check simple product 1 in minicart" stepKey="commentCheckSimpleProduct1InMinicart" after="addToCartFromStorefrontProductPage1"/> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="assertProduct1NameInMiniCart"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + </actionGroup> + + <!--Make simple product1 not visible individually--> + <updateData entity="SetProductVisibilityHidden" createDataKey="createSimpleProduct1" stepKey="updateSimpleProduct1"> + <requiredEntity createDataKey="createSimpleProduct1"/> + </updateData> + + <!--Go to simple product2 page--> + <amOnPage url="$$createSimpleProduct2.custom_attributes[url_key]$$.html" stepKey="navigateToSimpleProductPage2"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad2"/> + + <!--Add simple product2 to Shopping Cart for updating cart items--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + </actionGroup> + + <!--Check simple product1 in minicart--> + <comment userInput="Check hidden simple product 1 in minicart" stepKey="commentCheckHiddenSimpleProduct1InMinicart" after="addToCartFromStorefrontProductPage2"/> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="assertHiddenProduct1NameInMiniCart"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + </actionGroup> + + <!--Check simple product2 in minicart--> + <comment userInput="Check hidden simple product 2 in minicart" stepKey="commentCheckSimpleProduct2InMinicart" after="addToCartFromStorefrontProductPage2"/> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="assertProduct2NameInMiniCart"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + </actionGroup> + + <!--Delete simple product1 and simple product2--> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index 00d80cc2a94d9..35e0058440f6e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -158,13 +158,13 @@ <comment userInput="Check order summary in checkout" stepKey="commentCheckOrderSummaryInCheckout" after="guestCheckoutFillingShippingSection" /> <actionGroup ref="CheckOrderSummaryInCheckoutActionGroup" stepKey="guestCheckoutCheckOrderSummary" after="commentCheckOrderSummaryInCheckout"> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> + <argument name="subtotal" value="{{E2EB2CQuote.subtotal}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingTotal" value="E2EB2CQuote.shipping"/> + <argument name="shippingTotal" value="{{E2EB2CQuote.shipping}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> + <argument name="shippingMethod" value="{{E2EB2CQuote.shippingMethod}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="total" value="{{E2EB2CQuote.total}}"/> </actionGroup> <!-- Check ship to information in checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 05a6939941f3e..371826c9e7841 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -158,13 +158,13 @@ <comment userInput="Check order summary in checkout" stepKey="commentCheckOrderSummaryInCheckout" after="checkoutFillingShippingSection" /> <actionGroup ref="CheckOrderSummaryInCheckoutActionGroup" stepKey="checkoutCheckOrderSummary" after="commentCheckOrderSummaryInCheckout"> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> + <argument name="subtotal" value="{{E2EB2CQuote.subtotal}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingTotal" value="E2EB2CQuote.shipping"/> + <argument name="shippingTotal" value="{{E2EB2CQuote.shipping}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> + <argument name="shippingMethod" value="{{E2EB2CQuote.shippingMethod}}"/> <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="total" value="{{E2EB2CQuote.total}}"/> </actionGroup> <!-- Check ship to information in checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml new file mode 100644 index 0000000000000..aa90b7c1b54db --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontUpdateShoppingCartWhileUpdateMinicartTest"> + <annotations> + <features value="Checkout"/> + <title value="Check updating shopping cart while updating items from minicart"/> + <description value="Check updating shopping cart while updating items from minicart"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-97280"/> + <useCaseId value="MAGETWO-71344"/> + <group value="checkout"/> + </annotations> + + <before> + <!--Create product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct" /> + </after> + + <!--Add product to cart--> + <amOnPage url="$$createProduct.name$$.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Go to Shopping cart and check Qty--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCart"/> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" stepKey="grabQtyShoppingCart"/> + <assertEquals expected="1" actual="$grabQtyShoppingCart" stepKey="assertQtyShoppingCart"/> + + <!--Open minicart and change Qty--> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="openMiniCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.quantity}}" stepKey="waitForElementQty"/> + <pressKey selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::BACKSPACE]" stepKey="deleteFiled"/> + <fillField selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" userInput="5" stepKey="changeQty"/> + <click selector="{{StorefrontMinicartSection.itemQuantityUpdate($$createProduct.name$$)}}" stepKey="updateQty"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + + <!--Check Qty in shopping cart after updating--> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" stepKey="grabQtyShoppingCart1"/> + <assertEquals expected="5" actual="$grabQtyShoppingCart1" stepKey="assertQtyShoppingCart1"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 1ef7403e94ce1..e7c2ad3dd28a4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -15,7 +15,7 @@ <title value="Customer Checkout"/> <description value="Should be able to place an order as a customer."/> <severity value="CRITICAL"/> - <testCaseId value="#"/> + <testCaseId value="MC-5922"/> <group value="checkout"/> </annotations> <before> @@ -26,10 +26,13 @@ <createData entity="Simple_US_Customer" stepKey="simpleuscustomer"/> </before> <after> - <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + <!--Clear filters--> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingCustomerFilters"/> + + <actionGroup ref="logout" stepKey="logout"/> <deleteData createDataKey="simpleproduct1" stepKey="deleteProduct1"/> <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> - <deleteData createDataKey="simpleuscustomer" stepKey="deleteCustomer"/> </after> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> @@ -48,6 +51,8 @@ <click stepKey="s35" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> <waitForElement stepKey="s36" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> <click stepKey="s37" selector="{{CheckoutShippingMethodsSection.next}}" /> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <waitForPageLoad stepKey="s39"/> <waitForElement stepKey="s41" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> <see stepKey="s47" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{US_Address_TX.street[0]}}" /> @@ -76,6 +81,7 @@ <amOnPage stepKey="s96" url="{{AdminCustomerPage.url}}"/> <waitForPageLoad stepKey="s97"/> + <waitForElementVisible selector="{{AdminCustomerFiltersSection.filtersButton}}" time="30" stepKey="waitFiltersButton"/> <click stepKey="s98" selector="{{AdminCustomerFiltersSection.filtersButton}}"/> <fillField stepKey="s99" selector="{{AdminCustomerFiltersSection.emailInput}}" userInput="$$simpleuscustomer.email$$"/> <click stepKey="s100" selector="{{AdminCustomerFiltersSection.apply}}"/> @@ -163,7 +169,8 @@ <click stepKey="selectFirstShippingMethod1" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> <waitForElement stepKey="waitForShippingMethodSelect1" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> <click stepKey="clickNextOnShippingMethodLoad1" selector="{{CheckoutShippingMethodsSection.next}}" /> - <waitForPageLoad stepKey="waitForPaymentLoad1"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <waitForElement stepKey="waitForPlaceOrderButton1" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> <see stepKey="seeBillingAddressIsCorrect1" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{US_Address_NY.street[0]}}" /> <click stepKey="clickPlaceOrderButton1" selector="{{CheckoutPaymentSection.placeOrder}}" /> @@ -186,7 +193,8 @@ <click stepKey="selectFirstShippingMethod2" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> <waitForElement stepKey="waitForShippingMethodSelect2" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> <click stepKey="clickNextOnShippingMethodLoad2" selector="{{CheckoutShippingMethodsSection.next}}" /> - <waitForPageLoad stepKey="waitForPaymentLoad2"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> <waitForElement stepKey="waitForPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> <see stepKey="seeBillingAddressIsCorrect2" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" /> <click stepKey="clickPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" /> @@ -242,8 +250,7 @@ <click stepKey="clickNextButton" selector="{{CheckoutShippingMethodsSection.next}}" /> <waitForPageLoad stepKey="waitBillingForm"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.noQuotes}}" stepKey="waitMessage"/> - <see userInput="No Payment method available." stepKey="checkMessage"/> + <dontsee selector="{{CheckoutPaymentSection.paymentMethodByName('Check / Money order')}}" stepKey="paymentMethodDoesNotAvailable"/> <!-- Fill UK Address and verify that payment available and checkout successful --> <click selector="{{CheckoutHeaderSection.shippingMethodStep}}" stepKey="goToShipping" /> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml new file mode 100644 index 0000000000000..0cc0dcf38e312 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerCheckoutWithoutRegionTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via the Admin"/> + <title value="Shipping address is not validated in checkout when proceeding step as logged in user with default shipping address"/> + <description value="Shouldn't be able to place an order as a customer without state if it's required."/> + <severity value="CRITICAL"/> + <testCaseId value="#"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_GB_Customer" stepKey="createCustomer"/> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdmin"/> + <actionGroup ref="SelectCountriesWithRequiredRegion" stepKey="setCustomCountryWithRequiredRegion"> + <argument name="countries" value="CustomCountryWithRequiredRegion"/> + </actionGroup> + </before> + <after> + <actionGroup ref="SelectCountriesWithRequiredRegion" stepKey="setDefaultCountriesWithRequiredRegion"> + <argument name="countries" value="DefaultCountriesWithRequiredRegions"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="navigateToCheckoutPage"/> + + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNextButton"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='Please specify a regionId in shipping address.' stepKey="seeErrorMessages"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml index cc5e723c72ea0..8537e10ce5a03 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml @@ -79,8 +79,12 @@ <!--Verify New addresses in Customer's Address Book--> <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToCustomerAddressBook"/> - <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" - selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddresses"/> + <see userInput="{{UK_Not_Default_Address.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesStreet"/> + <see userInput="{{UK_Not_Default_Address.city}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesCity"/> + <see userInput="{{UK_Not_Default_Address.postcode}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesPostcode"/> <!--Order review page has address that was created during checkout--> <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="goToOrderReviewPage"/> <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml new file mode 100644 index 0000000000000..626f095604fa2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutDataPersistTest"> + <annotations> + <features value="Checkout"/> + <stories value="MAGETWO-95068: Checkout data (shipping address etc) not persistant after cart update"/> + <title value="Check that checkout data persist after cart update"/> + <description value="Checkout data should be persist after updating cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-96979"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Navigate to checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <!-- Fill shipping address --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart1"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Navigate to checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="assertGuestEmail"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="assertGuestFirstName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="assertGuestLastName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="assertGuestStreet"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="assertGuestCity"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="assertGuestRegion"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="assertGuestPostcode"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="assertGuestTelephone"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index f9533fd946f35..7b81f12624864 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -69,6 +69,25 @@ <see selector="{{AdminOrderDetailsInformationSection.shippingAddress}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeAdminOrderShippingAddress"/> <see selector="{{AdminOrderDetailsInformationSection.itemsOrdered}}" userInput="$$createProduct.name$$" stepKey="seeAdminOrderProduct"/> </test> + <test name="StorefrontGuestCheckoutWithSidebarDisabledTest" extends="StorefrontGuestCheckoutTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via Guest Checkout"/> + <title value="Guest Checkout when Cart sidebar disabled"/> + <description value="Should be able to place an order as a Guest when Cart sidebar is disabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-97001"/> + <group value="checkout"/> + </annotations> + <before> + <magentoCLI stepKey="disableSidebar" command="config:set checkout/sidebar/display 0" /> + </before> + <after> + <magentoCLI stepKey="enableSidebar" command="config:set checkout/sidebar/display 1" /> + </after> + <remove keyForRemoval="guestGoToCheckoutFromMinicart" /> + <actionGroup ref="GoToCheckoutFromCartActionGroup" stepKey="guestGoToCheckoutFromCart" after="seeCartQuantity" /> + </test> <test name="StorefrontGuestCheckoutTestWithRestrictedCountriesForPayment"> <annotations> <features value="Checkout"/> @@ -109,9 +128,10 @@ <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutFromMinicart" /> <!-- Fill US Address and verify that no payment available --> - <actionGroup ref="GuestCheckoutFillingShippingSectionUnavailablePaymentActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <actionGroup ref="GuestCheckoutWithSpecificCountryOptionForPaymentMethodActionGroup" stepKey="guestCheckoutFillingShippingSection"> <argument name="customerVar" value="CustomerEntityOne" /> <argument name="customerAddressVar" value="CustomerAddressSimple" /> + <argument name="paymentMethod" value="Check / Money order"/> </actionGroup> <!-- Fill UK Address and verify that payment available and checkout successful --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml new file mode 100644 index 0000000000000..778c1637a802d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontOnePageCheckoutDataWhenChangeQtyTest"> + <annotations> + <features value="Checkout"/> + <title value="One page Checkout Customer data when changing Product Qty"/> + <description value="One page Checkout Customer data when changing Product Qty"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96960"/> + <useCaseId value="MAGETWO-96850"/> + <group value="checkout"/> + </annotations> + <before> + <!--Create a product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!--Add product to cart and checkout--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="enterEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + + <!--Grab customer data to check it--> + <grabValueFrom selector="{{CheckoutShippingSection.email}}" stepKey="grabEmail"/> + <grabValueFrom selector="{{CheckoutShippingSection.firstName}}" stepKey="grabFirstName"/> + <grabValueFrom selector="{{CheckoutShippingSection.lastName}}" stepKey="grabLastName"/> + <grabValueFrom selector="{{CheckoutShippingSection.street}}" stepKey="grabStreet"/> + <grabValueFrom selector="{{CheckoutShippingSection.city}}" stepKey="grabCity"/> + <grabTextFrom selector="{{CheckoutShippingSection.region}}" stepKey="grabRegion"/> + <grabValueFrom selector="{{CheckoutShippingSection.postcode}}" stepKey="grabPostcode"/> + <grabValueFrom selector="{{CheckoutShippingSection.telephone}}" stepKey="grabTelephone"/> + + <!--Select shipping method and finalize checkout--> + <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> + + <!--Go to cart page, update qty and proceed to checkout--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <see userInput="Shopping Cart" stepKey="seeCartPageIsOpened"/> + <fillField selector="{{CheckoutCartProductSection.qty($$createProduct.name$$)}}" userInput="2" stepKey="updateProductQty"/> + <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickUpdateShoppingCart"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <grabValueFrom selector="{{CheckoutCartProductSection.qty($$createProduct.name$$)}}" stepKey="grabQty"/> + <assertEquals expected="2" actual="$grabQty" stepKey="assertQty"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + + <!--Check that form is filled with customer data--> + <grabValueFrom selector="{{CheckoutShippingSection.email}}" stepKey="grabEmail1"/> + <grabValueFrom selector="{{CheckoutShippingSection.firstName}}" stepKey="grabFirstName1"/> + <grabValueFrom selector="{{CheckoutShippingSection.lastName}}" stepKey="grabLastName1"/> + <grabValueFrom selector="{{CheckoutShippingSection.street}}" stepKey="grabStreet1"/> + <grabValueFrom selector="{{CheckoutShippingSection.city}}" stepKey="grabCity1"/> + <grabTextFrom selector="{{CheckoutShippingSection.region}}" stepKey="grabRegion1"/> + <grabValueFrom selector="{{CheckoutShippingSection.postcode}}" stepKey="grabPostcode1"/> + <grabValueFrom selector="{{CheckoutShippingSection.telephone}}" stepKey="grabTelephone1"/> + + <assertEquals expected="$grabEmail" actual="$grabEmail1" stepKey="assertEmail"/> + <assertEquals expected="$grabFirstName" actual="$grabFirstName1" stepKey="assertFirstName"/> + <assertEquals expected="$grabLastName" actual="$grabLastName1" stepKey="assertLastName"/> + <assertEquals expected="$grabStreet" actual="$grabStreet1" stepKey="assertStreet"/> + <assertEquals expected="$grabCity" actual="$grabCity1" stepKey="assertCity"/> + <assertEquals expected="$grabRegion" actual="$grabRegion1" stepKey="assertRegion"/> + <assertEquals expected="$grabPostcode" actual="$grabPostcode1" stepKey="assertPostcode"/> + <assertEquals expected="$grabTelephone" actual="$grabTelephone1" stepKey="assertTelephone"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml new file mode 100644 index 0000000000000..70f950f6f6c35 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest"> + <annotations> + <features value="Checkout"/> + <stories value="Estimator in Shopping cart must be pre-filled by Customer default shipping address for virtual quote"/> + <title value="Estimator in Shopping cart must be pre-filled by Customer default shipping address for virtual quote"/> + <description value="Estimator in Shopping cart must be pre-filled by Customer default shipping address for virtual quote"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-46795"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="defaultVirtualProduct" stepKey="createVirtualProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer_CA" stepKey="createCustomer"> + <field key="group_id">1</field> + </createData> + </before> + <after> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + <!-- Steps --> + <!-- Step 1: Go to Storefront as Customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + <!-- Step 2: Add virtual product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.custom_attributes[url_key]$)}}" stepKey="amOnPage"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$createVirtualProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!-- Step 3: Go to Shopping Cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingcart"/> + <!-- Step 4: Open Estimate Tax section --> + <click selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" stepKey="openEstimateTaxSection"/> + <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="checkCountry"/> + <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkState"/> + <scrollTo selector="{{CheckoutCartSummarySection.postcode}}" stepKey="scrollToPostCodeField"/> + <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> + <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> + <expectedResult type="string">{{US_Address_CA.postcode}}</expectedResult> + <actualResult type="variable">grabTextPostCode</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml index ace50aa3e6d33..2cc21df85ab67 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml @@ -36,6 +36,7 @@ <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <actionGroup ref="logout" stepKey="logout"/> <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> diff --git a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php index 54f77c95148ac..b54339aa2c1d8 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php @@ -35,7 +35,7 @@ class OnepageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $serializer; + private $serializerMock; protected function setUp() { @@ -49,7 +49,7 @@ protected function setUp() \Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class ); - $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); $this->model = new \Magento\Checkout\Block\Onepage( $contextMock, @@ -57,7 +57,8 @@ protected function setUp() $this->configProviderMock, [$this->layoutProcessorMock], [], - $this->serializer + $this->serializerMock, + $this->serializerMock ); } @@ -93,6 +94,7 @@ public function testGetJsLayout() $processedLayout = ['layout' => ['processed' => true]]; $jsonLayout = '{"layout":{"processed":true}}'; $this->layoutProcessorMock->expects($this->once())->method('process')->with([])->willReturn($processedLayout); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn($jsonLayout); $this->assertEquals($jsonLayout, $this->model->getJsLayout()); } @@ -101,6 +103,7 @@ public function testGetSerializedCheckoutConfig() { $checkoutConfig = ['checkout', 'config']; $this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($checkoutConfig); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn(json_encode($checkoutConfig)); $this->assertEquals(json_encode($checkoutConfig), $this->model->getSerializedCheckoutConfig()); } diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index d80f88786c87b..00bcd2a27005a 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -59,6 +59,7 @@ <item name="totalsSortOrder" xsi:type="object">Magento\Checkout\Block\Checkout\TotalsProcessor</item> <item name="directoryData" xsi:type="object">Magento\Checkout\Block\Checkout\DirectoryDataProcessor</item> </argument> + <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\JsonHexTag</argument> </arguments> </type> <type name="Magento\Checkout\Block\Cart\Totals"> diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index d4fadedf5d7a0..64b70e80bd84f 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -404,6 +404,10 @@ <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details/subtotal</item> <item name="displayArea" xsi:type="string">after_details</item> </item> + <item name="message" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details/message</item> + <item name="displayArea" xsi:type="string">item_message</item> + </item> </item> </item> </item> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml index e6d0260cf2305..20be9cd010c64 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml @@ -41,6 +41,14 @@ </div> <?= $block->getChildHtml('minicart.addons') ?> </div> + <?php else: ?> + <script> + require(['jquery'], function ($) { + $('a.action.showcart').click(function() { + $(document.body).trigger('processStart'); + }); + }); + </script> <?php endif ?> <script> window.checkout = <?= /* @escapeNotVerified */ $block->getSerializedConfig() ?>; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js index 22b37b2da0b2f..1858ce946fb07 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js @@ -10,7 +10,8 @@ */ define([ 'jquery', - 'Magento_Customer/js/customer-data' + 'Magento_Customer/js/customer-data', + 'jquery/jquery-storageapi' ], function ($, storage) { 'use strict'; @@ -23,6 +24,22 @@ define([ storage.set(cacheKey, data); }, + /** + * @return {*} + */ + initData = function () { + return { + 'selectedShippingAddress': null, //Selected shipping address pulled from persistence storage + 'shippingAddressFromData': null, //Shipping address pulled from persistence storage + 'newCustomerShippingAddress': null, //Shipping address pulled from persistence storage for customer + 'selectedShippingRate': null, //Shipping rate pulled from persistence storage + 'selectedPaymentMethod': null, //Payment method pulled from persistence storage + 'selectedBillingAddress': null, //Selected billing address pulled from persistence storage + 'billingAddressFromData': null, //Billing address pulled from persistence storage + 'newCustomerBillingAddress': null //Billing address pulled from persistence storage for new customer + }; + }, + /** * @return {*} */ @@ -30,17 +47,12 @@ define([ var data = storage.get(cacheKey)(); if ($.isEmptyObject(data)) { - data = { - 'selectedShippingAddress': null, //Selected shipping address pulled from persistence storage - 'shippingAddressFromData': null, //Shipping address pulled from persistence storage - 'newCustomerShippingAddress': null, //Shipping address pulled from persistence storage for customer - 'selectedShippingRate': null, //Shipping rate pulled from persistence storage - 'selectedPaymentMethod': null, //Payment method pulled from persistence storage - 'selectedBillingAddress': null, //Selected billing address pulled from persistence storage - 'billingAddressFromData': null, //Billing address pulled from persistence storage - 'newCustomerBillingAddress': null //Billing address pulled from persistence storage for new customer - }; - saveData(data); + data = $.initNamespaceStorage('mage-cache-storage').localStorage.get(cacheKey); + + if ($.isEmptyObject(data)) { + data = initData(); + saveData(data); + } } return data; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js index a880bd423ab2e..4ef39421440ce 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js @@ -22,6 +22,8 @@ define([ if (addressData.region && addressData.region['region_id']) { regionId = addressData.region['region_id']; + } else if (!addressData['region_id']) { + regionId = undefined; } else if ( /* eslint-disable */ addressData['country_id'] && addressData['country_id'] == window.checkoutConfig.defaultCountryId || diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index dde1ad72ba15e..e66c66006246c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -25,6 +25,7 @@ define([ } }, scrollHeight: 0, + shoppingCartUrl: window.checkout.shoppingCartUrl, /** * Create sidebar. @@ -227,6 +228,10 @@ define([ if (!_.isUndefined(productData)) { $(document).trigger('ajax:updateCartItemQty'); + + if (window.location.href === this.shoppingCartUrl) { + window.location.reload(false); + } } this._hideItemButton(elem); }, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js index 3b7fac3d19ea5..c0de643d3a223 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js @@ -180,7 +180,7 @@ define([ }, /** - * Resolves an initial sate of a login form. + * Resolves an initial state of a login form. * * @returns {Boolean} - initial visibility state. */ diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index 395d15bc02f36..c811d3a1e8369 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -265,7 +265,11 @@ define([ addressData, loginFormSelector = 'form[data-role=email-with-possible-login]', emailValidationResult = customer.isLoggedIn(), - field; + field, + country = registry.get(this.parentName + '.shippingAddress.shipping-address-fieldset.country_id'), + countryIndexedOptions = country.indexedOptions, + option = countryIndexedOptions[quote.shippingAddress().countryId], + messageContainer = registry.get('checkout.errors').messageContainer; if (!quote.shippingMethod()) { this.errorValidationMessage( @@ -318,6 +322,16 @@ define([ shippingAddress['save_in_address_book'] = 1; } selectShippingAddress(shippingAddress); + } else if (customer.isLoggedIn() && + option && + option['is_region_required'] && + !quote.shippingAddress().region + ) { + messageContainer.addErrorMessage({ + message: $t('Please specify a regionId in shipping address.') + }); + + return false; } if (!emailValidationResult) { diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/summary/item/details/message.js b/app/code/Magento/Checkout/view/frontend/web/js/view/summary/item/details/message.js new file mode 100644 index 0000000000000..ed41fd26c47ec --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/summary/item/details/message.js @@ -0,0 +1,30 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define(['uiComponent'], function (Component) { + 'use strict'; + + var quoteMessages = window.checkoutConfig.quoteMessages; + + return Component.extend({ + defaults: { + template: 'Magento_Checkout/summary/item/details/message' + }, + displayArea: 'item_message', + quoteMessages: quoteMessages, + + /** + * @param {Object} item + * @return {null} + */ + getMessage: function (item) { + if (this.quoteMessages[item['item_id']]) { + return this.quoteMessages[item['item_id']]; + } + + return null; + } + }); +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html index dd59bd78416c6..2491ee12d263c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html @@ -43,3 +43,6 @@ </div> <!-- /ko --> </div> +<!-- ko foreach: getRegion('item_message') --> + <!-- ko template: getTemplate() --><!-- /ko --> +<!-- /ko --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/message.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/message.html new file mode 100644 index 0000000000000..ea8f58cccd595 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/message.html @@ -0,0 +1,9 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="cart item message notice" if="getMessage($parents[1])"> + <div data-bind="text: getMessage($parents[1])"></div> +</div> diff --git a/app/code/Magento/Cms/Block/Adminhtml/Page/Grid/Renderer/Action/UrlBuilder.php b/app/code/Magento/Cms/Block/Adminhtml/Page/Grid/Renderer/Action/UrlBuilder.php index a19e3846a8d89..08ba2c3fff330 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Page/Grid/Renderer/Action/UrlBuilder.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Page/Grid/Renderer/Action/UrlBuilder.php @@ -33,15 +33,25 @@ public function __construct(\Magento\Framework\UrlInterface $frontendUrlBuilder) */ public function getUrl($routePath, $scope, $store) { - $this->frontendUrlBuilder->setScope($scope); - $href = $this->frontendUrlBuilder->getUrl( - $routePath, - [ - '_current' => false, - '_nosid' => true, - '_query' => [\Magento\Store\Model\StoreManagerInterface::PARAM_NAME => $store] - ] - ); + if ($scope) { + $this->frontendUrlBuilder->setScope($scope); + $href = $this->frontendUrlBuilder->getUrl( + $routePath, + [ + '_current' => false, + '_nosid' => true, + '_query' => [\Magento\Store\Model\StoreManagerInterface::PARAM_NAME => $store] + ] + ); + } else { + $href = $this->frontendUrlBuilder->getUrl( + $routePath, + [ + '_current' => false, + '_nosid' => true + ] + ); + } return $href; } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php index b5b035e0c67aa..655b3eb5b91b8 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php @@ -5,8 +5,11 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Edit CMS block action. + */ class Edit extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php index f92d38c0d856d..b7504a5c2b226 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php @@ -1,13 +1,15 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Index action. + */ class Index extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php index 92bc7ad71f590..ccdddc7c2b594 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php @@ -6,6 +6,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -14,7 +15,7 @@ /** * Class MassDelete */ -class MassDelete extends \Magento\Backend\App\Action +class MassDelete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php index 28db422fb51bc..5983594f876a2 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php @@ -1,13 +1,15 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Create CMS block action. + */ class NewAction extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php index 7526f59ce368b..0c6c6470398c1 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php @@ -1,12 +1,11 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Cms\Api\BlockRepositoryInterface; use Magento\Cms\Model\Block; @@ -15,6 +14,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; +/** + * Save CMS block action. + */ class Save extends \Magento\Cms\Controller\Adminhtml\Block implements HttpPostActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php index 16c99e9857c33..15bb40930536f 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php @@ -1,12 +1,16 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -class Delete extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Delete CMS page action. + */ +class Delete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php index e7bdfe9ed0e7d..f50fb2b19c00f 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php @@ -1,14 +1,16 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action; +/** + * Edit CMS page action. + */ class Edit extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php index 42fa4d62673c1..04557ddaeec78 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; +/** + * Index action. + */ class Index extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php index a1d32aa97a382..222849f97602d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -13,7 +14,7 @@ /** * Class MassDelete */ -class MassDelete extends \Magento\Backend\App\Action +class MassDelete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php index b07894e6a9de6..0a8c667d4d7b3 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php @@ -5,7 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php index 3f26769e4c9e9..e2cb8d984e01d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -13,7 +14,7 @@ /** * Class MassEnable */ -class MassEnable extends \Magento\Backend\App\Action +class MassEnable extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php index 407657b05c1c8..6a4f4951cef02 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php @@ -6,7 +6,12 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; -class NewAction extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpGetActionInterface; + +/** + * Create CMS page action. + */ +class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php index 5a7b900bf2d8b..37cb45753174f 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php @@ -1,17 +1,19 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Cms\Model\Page; use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\Exception\LocalizedException; +/** + * Save CMS page action. + */ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php index b268bee4c60a7..340f4b24dd16e 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page\Widget; use Magento\Framework\App\Action\HttpGetActionInterface; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action; +/** + * Chooser Source action. + */ class Chooser extends Action implements HttpPostActionInterface, HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php index a1de11c3c462e..5344472a79a9d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; /** * Delete image folder. */ -class DeleteFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +class DeleteFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php index 5c9aa2243bc6d..31b01ce115c21 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; /** * Upload image. */ -class Upload extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +class Upload extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml index de8a2adccc360..11d8bb23313fb 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml @@ -17,7 +17,7 @@ <element name="addNewPageButton" type="button" selector="#add" timeout="30"/> <element name="select" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//button[text()='Select']" parameterized="true"/> <element name="edit" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Edit']" parameterized="true"/> - <element name="preview" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Preview']" parameterized="true"/> + <element name="preview" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='View']" parameterized="true"/> <element name="clearAllButton" type="button" selector="//div[@class='admin__data-grid-header']//button[contains(text(), 'Clear all')]"/> <element name="activeFilters" type="button" selector="//div[@class='admin__data-grid-header']//span[contains(text(), 'Active filters:')]" /> <element name="spinner" type="input" selector='//div[@data-component="cms_page_listing.cms_page_listing.cms_page_columns"]'/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml index 7a358c61263d6..280c7dfd8263e 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCMSPageSection"> <element name="mediaDescription" type="text" selector=".column.main>p>img"/> - <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> + <element name="imageSource" type="text" selector="//img[contains(@src,'{{imageName}}')]" parameterized="true"/> <element name="mainTitle" type="text" selector="#maincontent .page-title"/> <element name="mainContent" type="text" selector="#maincontent"/> <element name="footerTop" type="text" selector="footer.page-footer"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml index c7ea85e441bb9..89431a0c7ce4d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml @@ -57,6 +57,7 @@ <element name="WysiwygArrow" type="button" selector="#d3lzaXd5Zw-- > .jstree-icon" /> <element name="checkIfWysiwygArrowExpand" type="button" selector="//li[@id='d3lzaXd5Zw--' and contains(@class,'jstree-closed')]" /> <element name="confirmDelete" type="button" selector=".action-primary.action-accept" /> + <element name="imageBlockByName" type="block" selector="//div[@data-row='file'][contains(., '{{imageName}}')]" parameterized="true"/> </section> <section name="VariableSection"> <element name="InsertWidget" type="button" selector="#insert_variable"/> diff --git a/app/code/Magento/Config/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php index 81e39a83296d7..2a29fa33feb74 100644 --- a/app/code/Magento/Config/Block/System/Config/Form.php +++ b/app/code/Magento/Config/Block/System/Config/Form.php @@ -134,6 +134,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic * @param \Magento\Config\Block\System\Config\Form\Fieldset\Factory $fieldsetFactory * @param \Magento\Config\Block\System\Config\Form\Field\Factory $fieldFactory * @param array $data + * @param SettingChecker|null $settingChecker */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -143,13 +144,15 @@ public function __construct( \Magento\Config\Model\Config\Structure $configStructure, \Magento\Config\Block\System\Config\Form\Fieldset\Factory $fieldsetFactory, \Magento\Config\Block\System\Config\Form\Field\Factory $fieldFactory, - array $data = [] + array $data = [], + SettingChecker $settingChecker = null ) { parent::__construct($context, $registry, $formFactory, $data); $this->_configFactory = $configFactory; $this->_configStructure = $configStructure; $this->_fieldsetFactory = $fieldsetFactory; $this->_fieldFactory = $fieldFactory; + $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class); $this->_scopeLabels = [ self::SCOPE_DEFAULT => __('[GLOBAL]'), @@ -158,18 +161,6 @@ public function __construct( ]; } - /** - * @deprecated 100.1.2 - * @return SettingChecker - */ - private function getSettingChecker() - { - if ($this->settingChecker === null) { - $this->settingChecker = ObjectManager::getInstance()->get(SettingChecker::class); - } - return $this->settingChecker; - } - /** * Initialize objects required to render config form * @@ -366,9 +357,8 @@ protected function _initElement( $sharedClass = $this->_getSharedCssClass($field); $requiresClass = $this->_getRequiresCssClass($field, $fieldPrefix); + $isReadOnly = $this->isReadOnly($field, $path); - $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath()) - ?: $this->getSettingChecker()->isReadOnly($path, $this->getScope(), $this->getStringScopeCode()); $formField = $fieldset->addField( $elementId, $field->getType(), @@ -417,7 +407,7 @@ private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Fie { $data = $this->getAppConfigDataValue($path); - $placeholderValue = $this->getSettingChecker()->getPlaceholderValue( + $placeholderValue = $this->settingChecker->getPlaceholderValue( $path, $this->getScope(), $this->getStringScopeCode() @@ -541,7 +531,7 @@ public function getConfigValue($path) } /** - * @return \Magento\Backend\Block\Widget\Form|\Magento\Framework\View\Element\AbstractBlock + * @inheritdoc */ protected function _beforeToHtml() { @@ -718,6 +708,7 @@ protected function _getAdditionalElementTypes() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getSectionCode() { @@ -729,6 +720,7 @@ public function getSectionCode() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getWebsiteCode() { @@ -740,6 +732,7 @@ public function getWebsiteCode() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getStoreCode() { @@ -797,6 +790,26 @@ private function getAppConfig() return $this->appConfig; } + /** + * Check Path is Readonly + * + * @param \Magento\Config\Model\Config\Structure\Element\Field $field + * @param string $path + * @return boolean + */ + private function isReadOnly(\Magento\Config\Model\Config\Structure\Element\Field $field, $path) + { + $isReadOnly = $this->settingChecker->isReadOnly( + $path, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + if (!$isReadOnly) { + $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath()) + ?: $this->settingChecker->isReadOnly($path, $this->getScope(), $this->getStringScopeCode()); + } + return $isReadOnly; + } + /** * Retrieve deployment config data value by path * diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php index d7d513bfad423..86ae1f96749df 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php @@ -7,17 +7,19 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Console\Command\ConfigSetCommand; +use Magento\Config\Model\Config\Factory as ConfigFactory; use Magento\Framework\App\Config\ConfigPathResolver; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Config\Model\PreparedValueFactory; -use Magento\Framework\App\Config\Value; /** * Processes default flow of config:set command. + * * This processor saves the value of configuration into database. * - * {@inheritdoc} + * @inheritdoc * @api * @since 100.2.0 */ @@ -44,26 +46,36 @@ class DefaultProcessor implements ConfigSetProcessorInterface */ private $preparedValueFactory; + /** + * @var ConfigFactory + */ + private $configFactory; + /** * @param PreparedValueFactory $preparedValueFactory The factory for prepared value * @param DeploymentConfig $deploymentConfig The deployment configuration reader * @param ConfigPathResolver $configPathResolver The resolver for configuration paths according to source type + * @param ConfigFactory|null $configFactory */ public function __construct( PreparedValueFactory $preparedValueFactory, DeploymentConfig $deploymentConfig, - ConfigPathResolver $configPathResolver + ConfigPathResolver $configPathResolver, + ConfigFactory $configFactory = null ) { $this->preparedValueFactory = $preparedValueFactory; $this->deploymentConfig = $deploymentConfig; $this->configPathResolver = $configPathResolver; + + $this->configFactory = $configFactory ?? ObjectManager::getInstance()->get(ConfigFactory::class); } /** * Processes database flow of config:set command. + * * Requires installed application. * - * {@inheritdoc} + * @inheritdoc * @since 100.2.0 */ public function process($path, $value, $scope, $scopeCode) @@ -78,12 +90,12 @@ public function process($path, $value, $scope, $scopeCode) } try { - /** @var Value $backendModel */ - $backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode); - if ($backendModel instanceof Value) { - $resourceModel = $backendModel->getResource(); - $resourceModel->save($backendModel); - } + $config = $this->configFactory->create([ + 'scope' => $scope, + 'scope_code' => $scopeCode, + ]); + $config->setDataByPath($path, $value); + $config->save(); } catch (\Exception $exception) { throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception); } diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index a9c400c46643d..b1074e92cc949 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -9,15 +9,32 @@ use Magento\Config\Model\Config\Structure\Element\Group; use Magento\Config\Model\Config\Structure\Element\Field; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ScopeInterface; +use Magento\Framework\App\ScopeResolverPool; +use Magento\Store\Model\ScopeInterface as StoreScopeInterface; +use Magento\Store\Model\ScopeTypeNormalizer; /** * Backend config model * * Used to save configuration + * * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 + * @method string getSection() + * @method void setSection(string $section) + * @method string getWebsite() + * @method void setWebsite(string $website) + * @method string getStore() + * @method void setStore(string $store) + * @method string getScope() + * @method void setScope(string $scope) + * @method int getScopeId() + * @method void setScopeId(int $scopeId) + * @method string getScopeCode() + * @method void setScopeCode(string $scopeCode) */ class Config extends \Magento\Framework\DataObject { @@ -87,6 +104,16 @@ class Config extends \Magento\Framework\DataObject */ private $settingChecker; + /** + * @var ScopeResolverPool + */ + private $scopeResolverPool; + + /** + * @var ScopeTypeNormalizer + */ + private $scopeTypeNormalizer; + /** * @param \Magento\Framework\App\Config\ReinitableConfigInterface $config * @param \Magento\Framework\Event\ManagerInterface $eventManager @@ -97,6 +124,9 @@ class Config extends \Magento\Framework\DataObject * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param Config\Reader\Source\Deployed\SettingChecker|null $settingChecker * @param array $data + * @param ScopeResolverPool|null $scopeResolverPool + * @param ScopeTypeNormalizer|null $scopeTypeNormalizer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Config\ReinitableConfigInterface $config, @@ -107,7 +137,9 @@ public function __construct( \Magento\Framework\App\Config\ValueFactory $configValueFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, SettingChecker $settingChecker = null, - array $data = [] + array $data = [], + ScopeResolverPool $scopeResolverPool = null, + ScopeTypeNormalizer $scopeTypeNormalizer = null ) { parent::__construct($data); $this->_eventManager = $eventManager; @@ -117,7 +149,12 @@ public function __construct( $this->_configLoader = $configLoader; $this->_configValueFactory = $configValueFactory; $this->_storeManager = $storeManager; - $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class); + $this->settingChecker = $settingChecker + ?? ObjectManager::getInstance()->get(SettingChecker::class); + $this->scopeResolverPool = $scopeResolverPool + ?? ObjectManager::getInstance()->get(ScopeResolverPool::class); + $this->scopeTypeNormalizer = $scopeTypeNormalizer + ?? ObjectManager::getInstance()->get(ScopeTypeNormalizer::class); } /** @@ -505,9 +542,8 @@ public function setDataByPath($path, $value) } /** - * Get scope name and scopeId + * Set scope data * - * @todo refactor to scope resolver * @return void */ private function initScope() @@ -515,31 +551,66 @@ private function initScope() if ($this->getSection() === null) { $this->setSection(''); } + + $scope = $this->retrieveScope(); + $this->setScope($this->scopeTypeNormalizer->normalize($scope->getScopeType())); + $this->setScopeCode($scope->getCode()); + $this->setScopeId($scope->getId()); + if ($this->getWebsite() === null) { - $this->setWebsite(''); + $this->setWebsite(StoreScopeInterface::SCOPE_WEBSITES === $this->getScope() ? $scope->getId() : ''); } if ($this->getStore() === null) { - $this->setStore(''); + $this->setStore(StoreScopeInterface::SCOPE_STORES === $this->getScope() ? $scope->getId() : ''); } + } - if ($this->getStore()) { - $scope = 'stores'; - $store = $this->_storeManager->getStore($this->getStore()); - $scopeId = (int)$store->getId(); - $scopeCode = $store->getCode(); - } elseif ($this->getWebsite()) { - $scope = 'websites'; - $website = $this->_storeManager->getWebsite($this->getWebsite()); - $scopeId = (int)$website->getId(); - $scopeCode = $website->getCode(); + /** + * Retrieve scope from initial data + * + * @return ScopeInterface + */ + private function retrieveScope(): ScopeInterface + { + $scopeType = $this->getScope(); + if (!$scopeType) { + switch (true) { + case $this->getStore(): + $scopeType = StoreScopeInterface::SCOPE_STORES; + $scopeIdentifier = $this->getStore(); + break; + case $this->getWebsite(): + $scopeType = StoreScopeInterface::SCOPE_WEBSITES; + $scopeIdentifier = $this->getWebsite(); + break; + default: + $scopeType = ScopeInterface::SCOPE_DEFAULT; + $scopeIdentifier = null; + break; + } } else { - $scope = 'default'; - $scopeId = 0; - $scopeCode = ''; + switch (true) { + case $this->getScopeId() !== null: + $scopeIdentifier = $this->getScopeId(); + break; + case $this->getScopeCode() !== null: + $scopeIdentifier = $this->getScopeCode(); + break; + case $this->getStore() !== null: + $scopeIdentifier = $this->getStore(); + break; + case $this->getWebsite() !== null: + $scopeIdentifier = $this->getWebsite(); + break; + default: + $scopeIdentifier = null; + break; + } } - $this->setScope($scope); - $this->setScopeId($scopeId); - $this->setScopeCode($scopeCode); + $scope = $this->scopeResolverPool->get($scopeType) + ->getScope($scopeIdentifier); + + return $scope; } /** diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php index 4ae66bfd9692b..25303093ace5d 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php +++ b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php @@ -14,6 +14,8 @@ namespace Magento\Config\Model\Config\Backend\Currency; /** + * Base currency class + * * @api * @since 100.0.2 */ @@ -26,18 +28,19 @@ abstract class AbstractCurrency extends \Magento\Framework\App\Config\Value */ protected function _getAllowedCurrencies() { - if (!$this->isFormData() || $this->getData('groups/options/fields/allow/inherit')) { - return explode( + $allowValue = $this->getData('groups/options/fields/allow/value'); + $allowedCurrencies = $allowValue === null || $this->getData('groups/options/fields/allow/inherit') + ? explode( ',', (string)$this->_config->getValue( \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW, $this->getScope(), $this->getScopeId() ) - ); - } + ) + : (array) $allowValue; - return (array)$this->getData('groups/options/fields/allow/value'); + return $allowedCurrencies; } /** diff --git a/app/code/Magento/Config/Model/Config/Structure.php b/app/code/Magento/Config/Model/Config/Structure.php index 5c74220051ba9..a380dc82a7c5e 100644 --- a/app/code/Magento/Config/Model/Config/Structure.php +++ b/app/code/Magento/Config/Model/Config/Structure.php @@ -337,7 +337,6 @@ protected function _getGroupFieldPathsByAttribute(array $fields, $parentPath, $a /** * Collects config paths and their structure paths from configuration files. * Returns the map of config paths and their structure paths. - * * All paths are declared in module's system.xml. * * ```xml @@ -394,7 +393,7 @@ private function getFieldsRecursively(array $elements = []) foreach ($elements as $element) { if (isset($element['children'])) { - $result = array_replace_recursive( + $result = array_merge_recursive( $result, $this->getFieldsRecursively($element['children']) ); diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml index fca48dfb49cfb..f05cf5be3448e 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml @@ -37,4 +37,17 @@ <click selector="#save" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForSavingConfig"/> </actionGroup> + + <actionGroup name="SelectCountriesWithRequiredRegion"> + <arguments> + <argument name="countries" type="countryArray"/> + </arguments> + <amOnPage url="{{AdminConfigGeneralPage.url}}" stepKey="navigateToAdminConfigGeneralPage"/> + <conditionalClick selector="{{StateOptionsSection.stateOptions}}" dependentSelector="{{StateOptionsSection.countriesWithRequiredRegions}}" visible="false" stepKey="expandStateOptionsTab" /> + <waitForAjaxLoad stepKey="waitForAjax"/> + <scrollTo selector="{{StateOptionsSection.countriesWithRequiredRegions}}" stepKey="scrollToForm"/> + <selectOption selector="{{StateOptionsSection.countriesWithRequiredRegions}}" parameterArray="[{{countries.country}}]" stepKey="selectCountriesWithRequiredRegion"/> + <click selector="#save" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForSavingConfig"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/Data/WebUrlOptionsConfigData.xml b/app/code/Magento/Config/Test/Mftf/Data/WebUrlOptionsConfigData.xml new file mode 100644 index 0000000000000..eda0eb904be86 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Data/WebUrlOptionsConfigData.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultWebUrlOptionsConfig" type="web_url_use_store"> + <requiredEntity type="url_use_store_value">DefaultConfigWebUrlOptions</requiredEntity> + </entity> + <entity name="DefaultConfigWebUrlOptions" type="url_use_store_value"> + <data key="value">0</data> + </entity> + + <entity name="EnableWebUrlOptionsConfig" type="web_url_use_store"> + <requiredEntity type="url_use_store_value">WebUrlOptionsYes</requiredEntity> + </entity> + <entity name="WebUrlOptionsYes" type="url_use_store_value"> + <data key="value">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/web_url_options_config-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/web_url_options_config-meta.xml new file mode 100644 index 0000000000000..fc14ba7bbaba3 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Metadata/web_url_options_config-meta.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="WebUrlOptionsConfig" dataType="web_url_use_store" type="create" auth="adminFormKey" url="/admin/system_config/save/section/web/" + method="POST" successRegex="/messages-message-success/" returnRegex=""> + <object key="groups" dataType="web_url_use_store"> + <object key="url" dataType="web_url_use_store"> + <object key="fields" dataType="web_url_use_store"> + <object key="use_store" dataType="url_use_store_value"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml index 88532dc091f45..66b40f74d8e98 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -38,4 +38,9 @@ <element name="countryOptionsOpen" type="button" selector="#general_country-head.open"/> <element name="topDestinations" type="select" selector="#general_country_destinations"/> </section> + <section name="StateOptionsSection"> + <element name="stateOptions" type="button" selector="#general_region-head"/> + <element name="countriesWithRequiredRegions" type="select" selector="#general_region_state_required"/> + <element name="allowToChooseState" type="select" selector="general_region_display_all"/> + </section> </sections> diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php index 93650dd62657c..4e260b0fb2bb1 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php @@ -102,6 +102,9 @@ protected function setUp() \Magento\Config\Block\System\Config\Form\Fieldset\Factory::class ); $this->_fieldFactoryMock = $this->createMock(\Magento\Config\Block\System\Config\Form\Field\Factory::class); + $settingCheckerMock = $this->getMockBuilder(SettingChecker::class) + ->disableOriginalConstructor() + ->getMock(); $this->_coreConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->_backendConfigMock = $this->createMock(\Magento\Config\Model\Config::class); @@ -153,6 +156,7 @@ protected function setUp() 'fieldsetFactory' => $this->_fieldsetFactoryMock, 'fieldFactory' => $this->_fieldFactoryMock, 'context' => $context, + 'settingChecker' => $settingCheckerMock, ]; $objectArguments = $helper->getConstructArguments(\Magento\Config\Block\System\Config\Form::class, $data); @@ -532,7 +536,7 @@ public function testInitFields( $elementVisibilityMock = $this->getMockBuilder(ElementVisibilityInterface::class) ->getMockForAbstractClass(); - $elementVisibilityMock->expects($this->once()) + $elementVisibilityMock->expects($this->any()) ->method('isDisabled') ->willReturn($isDisabled); diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php index 984e0fe842687..edb76c067bf35 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php @@ -7,13 +7,14 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Console\Command\ConfigSet\DefaultProcessor; +use Magento\Config\Model\Config; +use Magento\Config\Model\Config\Factory as ConfigFactory; use Magento\Framework\App\Config\ConfigPathResolver; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Store\Model\ScopeInterface; use Magento\Config\Model\PreparedValueFactory; use Magento\Framework\App\Config\Value; -use Magento\Framework\App\Config\ValueInterface; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use PHPUnit_Framework_MockObject_MockObject as Mock; @@ -55,17 +56,18 @@ class DefaultProcessorTest extends \PHPUnit\Framework\TestCase */ private $resourceModelMock; + /** + * @var ConfigFactory|Mock + */ + private $configFactory; + /** * @inheritdoc */ protected function setUp() { - $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) - ->disableOriginalConstructor() - ->getMock(); - $this->configPathResolverMock = $this->getMockBuilder(ConfigPathResolver::class) - ->disableOriginalConstructor() - ->getMock(); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + $this->configPathResolverMock = $this->createMock(ConfigPathResolver::class); $this->resourceModelMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['save']) @@ -74,14 +76,14 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['getResource']) ->getMock(); - $this->preparedValueFactoryMock = $this->getMockBuilder(PreparedValueFactory::class) - ->disableOriginalConstructor() - ->getMock(); + $this->preparedValueFactoryMock = $this->createMock(PreparedValueFactory::class); + $this->configFactory = $this->createMock(ConfigFactory::class); $this->model = new DefaultProcessor( $this->preparedValueFactoryMock, $this->deploymentConfigMock, - $this->configPathResolverMock + $this->configPathResolverMock, + $this->configFactory ); } @@ -98,15 +100,16 @@ public function testProcess($path, $value, $scope, $scopeCode) { $this->configMockForProcessTest($path, $scope, $scopeCode); - $this->preparedValueFactoryMock->expects($this->once()) + $config = $this->createMock(Config::class); + $this->configFactory->expects($this->once()) ->method('create') - ->willReturn($this->valueMock); - $this->valueMock->expects($this->once()) - ->method('getResource') - ->willReturn($this->resourceModelMock); - $this->resourceModelMock->expects($this->once()) + ->with(['scope' => $scope, 'scope_code' => $scopeCode]) + ->willReturn($config); + $config->expects($this->once()) + ->method('setDataByPath') + ->with($path, $value); + $config->expects($this->once()) ->method('save') - ->with($this->valueMock) ->willReturnSelf(); $this->model->process($path, $value, $scope, $scopeCode); @@ -124,28 +127,6 @@ public function processDataProvider() ]; } - public function testProcessWithWrongValueInstance() - { - $path = 'test/test/test'; - $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; - $scopeCode = null; - $value = 'value'; - $valueInterfaceMock = $this->getMockBuilder(ValueInterface::class) - ->getMockForAbstractClass(); - - $this->configMockForProcessTest($path, $scope, $scopeCode); - - $this->preparedValueFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($valueInterfaceMock); - $this->valueMock->expects($this->never()) - ->method('getResource'); - $this->resourceModelMock->expects($this->never()) - ->method('save'); - - $this->model->process($path, $value, $scope, $scopeCode); - } - /** * @param string $path * @param string $scope @@ -185,6 +166,9 @@ public function testProcessLockedValue() ->method('resolve') ->willReturn('system/default/test/test/test'); + $this->configFactory->expects($this->never()) + ->method('create'); + $this->model->process($path, $value, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null); } } diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/StructureTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/StructureTest.php index 6c059f4b69b70..a17faf8f35883 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/StructureTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/StructureTest.php @@ -418,6 +418,7 @@ public function testGetFieldPaths() 'field_2' ], 'field_3' => [ + 'field_3', 'field_3' ], 'field_3_1' => [ diff --git a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php index d0568f48ded1e..bdcb44b756bb2 100644 --- a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Config\Test\Unit\Model; +use PHPUnit\Framework\MockObject\MockObject; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -13,130 +15,158 @@ class ConfigTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Config\Model\Config */ - protected $_model; + private $model; + + /** + * @var \Magento\Framework\Event\ManagerInterface|MockObject + */ + private $eventManagerMock; + + /** + * @var \Magento\Config\Model\Config\Structure\Reader|MockObject + */ + private $structureReaderMock; + + /** + * @var \Magento\Framework\DB\TransactionFactory|MockObject + */ + private $transFactoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ReinitableConfigInterface|MockObject */ - protected $_eventManagerMock; + private $appConfigMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Loader|MockObject */ - protected $_structureReaderMock; + private $configLoaderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ValueFactory|MockObject */ - protected $_transFactoryMock; + private $dataFactoryMock; /** - * @var \Magento\Framework\App\Config\ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\StoreManagerInterface|MockObject */ - protected $_appConfigMock; + private $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Structure|MockObject */ - protected $_applicationMock; + private $configStructure; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker|MockObject */ - protected $_configLoaderMock; + private $settingsChecker; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\ScopeResolverPool|MockObject */ - protected $_dataFactoryMock; + private $scopeResolverPool; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var \Magento\Framework\App\ScopeResolverInterface|MockObject */ - protected $_storeManager; + private $scopeResolver; /** - * @var \Magento\Config\Model\Config\Structure + * @var \Magento\Framework\App\ScopeInterface|MockObject */ - protected $_configStructure; + private $scope; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\ScopeTypeNormalizer|MockObject */ - private $_settingsChecker; + private $scopeTypeNormalizer; protected function setUp() { - $this->_eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); - $this->_structureReaderMock = $this->createPartialMock( + $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + $this->structureReaderMock = $this->createPartialMock( \Magento\Config\Model\Config\Structure\Reader::class, ['getConfiguration'] ); - $this->_configStructure = $this->createMock(\Magento\Config\Model\Config\Structure::class); + $this->configStructure = $this->createMock(\Magento\Config\Model\Config\Structure::class); - $this->_structureReaderMock->expects( + $this->structureReaderMock->expects( $this->any() )->method( 'getConfiguration' )->will( - $this->returnValue($this->_configStructure) + $this->returnValue($this->configStructure) ); - $this->_transFactoryMock = $this->createPartialMock( + $this->transFactoryMock = $this->createPartialMock( \Magento\Framework\DB\TransactionFactory::class, ['create', 'addObject'] ); - $this->_appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class); - $this->_configLoaderMock = $this->createPartialMock( + $this->appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class); + $this->configLoaderMock = $this->createPartialMock( \Magento\Config\Model\Config\Loader::class, ['getConfigByPath'] ); - $this->_dataFactoryMock = $this->createMock(\Magento\Framework\App\Config\ValueFactory::class); + $this->dataFactoryMock = $this->createMock(\Magento\Framework\App\Config\ValueFactory::class); - $this->_storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->_settingsChecker = $this + $this->settingsChecker = $this ->createMock(\Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker::class); - $this->_model = new \Magento\Config\Model\Config( - $this->_appConfigMock, - $this->_eventManagerMock, - $this->_configStructure, - $this->_transFactoryMock, - $this->_configLoaderMock, - $this->_dataFactoryMock, - $this->_storeManager, - $this->_settingsChecker + $this->scopeResolverPool = $this->createMock(\Magento\Framework\App\ScopeResolverPool::class); + $this->scopeResolver = $this->createMock(\Magento\Framework\App\ScopeResolverInterface::class); + $this->scopeResolverPool->method('get') + ->willReturn($this->scopeResolver); + $this->scope = $this->createMock(\Magento\Framework\App\ScopeInterface::class); + $this->scopeResolver->method('getScope') + ->willReturn($this->scope); + + $this->scopeTypeNormalizer = $this->createMock(\Magento\Store\Model\ScopeTypeNormalizer::class); + + $this->model = new \Magento\Config\Model\Config( + $this->appConfigMock, + $this->eventManagerMock, + $this->configStructure, + $this->transFactoryMock, + $this->configLoaderMock, + $this->dataFactoryMock, + $this->storeManager, + $this->settingsChecker, + [], + $this->scopeResolverPool, + $this->scopeTypeNormalizer ); } public function testSaveDoesNotDoAnythingIfGroupsAreNotPassed() { - $this->_configLoaderMock->expects($this->never())->method('getConfigByPath'); - $this->_model->save(); + $this->configLoaderMock->expects($this->never())->method('getConfigByPath'); + $this->model->save(); } public function testSaveEmptiesNonSetArguments() { - $this->_structureReaderMock->expects($this->never())->method('getConfiguration'); - $this->assertNull($this->_model->getSection()); - $this->assertNull($this->_model->getWebsite()); - $this->assertNull($this->_model->getStore()); - $this->_model->save(); - $this->assertSame('', $this->_model->getSection()); - $this->assertSame('', $this->_model->getWebsite()); - $this->assertSame('', $this->_model->getStore()); + $this->structureReaderMock->expects($this->never())->method('getConfiguration'); + $this->assertNull($this->model->getSection()); + $this->assertNull($this->model->getWebsite()); + $this->assertNull($this->model->getStore()); + $this->model->save(); + $this->assertSame('', $this->model->getSection()); + $this->assertSame('', $this->model->getWebsite()); + $this->assertSame('', $this->model->getStore()); } public function testSaveToCheckAdminSystemConfigChangedSectionEvent() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_eventManagerMock->expects( + $this->eventManagerMock->expects( $this->at(0) )->method( 'dispatch' @@ -145,7 +175,7 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent() $this->arrayHasKey('website') ); - $this->_eventManagerMock->expects( + $this->eventManagerMock->expects( $this->at(0) )->method( 'dispatch' @@ -154,20 +184,20 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent() $this->arrayHasKey('store') ); - $this->_model->setGroups(['1' => ['data']]); - $this->_model->save(); + $this->model->setGroups(['1' => ['data']]); + $this->model->save(); } public function testDoNotSaveReadOnlyFields() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true)); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); - $this->_model->setSection('section'); + $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); + $this->model->setSection('section'); $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class); $group->method('getPath')->willReturn('section/1'); @@ -176,15 +206,15 @@ public function testDoNotSaveReadOnlyFields() $field->method('getGroupPath')->willReturn('section/1'); $field->method('getId')->willReturn('key'); - $this->_configStructure->expects($this->at(0)) + $this->configStructure->expects($this->at(0)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(1)) + $this->configStructure->expects($this->at(1)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(2)) + $this->configStructure->expects($this->at(2)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); @@ -193,28 +223,28 @@ public function testDoNotSaveReadOnlyFields() \Magento\Framework\App\Config\Value::class, ['addData'] ); - $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); + $this->dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); - $this->_transFactoryMock->expects($this->never())->method('addObject'); + $this->transFactoryMock->expects($this->never())->method('addObject'); $backendModel->expects($this->never())->method('addData'); - $this->_model->save(); + $this->model->save(); } public function testSaveToCheckScopeDataSet() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_eventManagerMock->expects($this->at(0)) + $this->eventManagerMock->expects($this->at(0)) ->method('dispatch') ->with( $this->equalTo('admin_system_config_changed_section_section'), $this->arrayHasKey('website') ); - $this->_eventManagerMock->expects($this->at(0)) + $this->eventManagerMock->expects($this->at(0)) ->method('dispatch') ->with( $this->equalTo('admin_system_config_changed_section_section'), @@ -228,36 +258,51 @@ public function testSaveToCheckScopeDataSet() $field->method('getGroupPath')->willReturn('section/1'); $field->method('getId')->willReturn('key'); - $this->_configStructure->expects($this->at(0)) + $this->configStructure->expects($this->at(0)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(1)) + $this->configStructure->expects($this->at(1)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(2)) + $this->configStructure->expects($this->at(2)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); - $this->_configStructure->expects($this->at(3)) + $this->configStructure->expects($this->at(3)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(4)) + $this->configStructure->expects($this->at(4)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); + $this->scopeResolver->expects($this->atLeastOnce()) + ->method('getScope') + ->with('1') + ->willReturn($this->scope); + $this->scope->expects($this->atLeastOnce()) + ->method('getScopeType') + ->willReturn('website'); + $this->scope->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(1); + $this->scope->expects($this->atLeastOnce()) + ->method('getCode') + ->willReturn('website_code'); + $this->scopeTypeNormalizer->expects($this->atLeastOnce()) + ->method('normalize') + ->with('website') + ->willReturn('websites'); $website = $this->createMock(\Magento\Store\Model\Website::class); - $website->expects($this->any())->method('getCode')->will($this->returnValue('website_code')); - $this->_storeManager->expects($this->any())->method('getWebsite')->will($this->returnValue($website)); - $this->_storeManager->expects($this->any())->method('getWebsites')->will($this->returnValue([$website])); - $this->_storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true)); + $this->storeManager->expects($this->any())->method('getWebsites')->will($this->returnValue([$website])); + $this->storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true)); - $this->_model->setWebsite('website'); - $this->_model->setSection('section'); - $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); + $this->model->setWebsite('1'); + $this->model->setSection('section'); + $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); $backendModel = $this->createPartialMock( \Magento\Framework\App\Config\Value::class, @@ -270,7 +315,7 @@ public function testSaveToCheckScopeDataSet() 'groups' => [1 => ['fields' => ['key' => ['data']]]], 'group_id' => null, 'scope' => 'websites', - 'scope_id' => 0, + 'scope_id' => 1, 'scope_code' => 'website_code', 'field_config' => null, 'fieldset_data' => ['key' => null], @@ -280,16 +325,16 @@ public function testSaveToCheckScopeDataSet() ->with('section/1/key') ->will($this->returnValue($backendModel)); - $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); + $this->dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); - $this->_model->save(); + $this->model->save(); } public function testSetDataByPath() { $value = 'value'; $path = '<section>/<group>/<field>'; - $this->_model->setDataByPath($path, $value); + $this->model->setDataByPath($path, $value); $expected = [ 'section' => '<section>', 'groups' => [ @@ -300,7 +345,7 @@ public function testSetDataByPath() ], ], ]; - $this->assertSame($expected, $this->_model->getData()); + $this->assertSame($expected, $this->model->getData()); } /** @@ -309,7 +354,7 @@ public function testSetDataByPath() */ public function testSetDataByPathEmpty() { - $this->_model->setDataByPath('', 'value'); + $this->model->setDataByPath('', 'value'); } /** @@ -324,7 +369,7 @@ public function testSetDataByPathWrongDepth($path, $expectedException) $this->expectException('\UnexpectedValueException'); $this->expectExceptionMessage($expectedException); $value = 'value'; - $this->_model->setDataByPath($path, $value); + $this->model->setDataByPath($path, $value); } /** diff --git a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php index fdbd8560c2664..3f4565771e70b 100644 --- a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php @@ -248,7 +248,7 @@ public function __construct( */ protected function _addAttributeParams($attrSetName, array $attrParams, $attribute) { - // save super attributes for simplier and quicker search in future + // save super attributes for simpler and quicker search in future if ('select' == $attrParams['type'] && 1 == $attrParams['is_global']) { $this->_superAttributes[$attrParams['code']] = $attrParams; } diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php new file mode 100644 index 0000000000000..92b7ab0d88ea8 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; + +/** + * Extender of product identities for child of configurable products + */ +class ProductIdentitiesExtender +{ + /** + * @var Configurable + */ + private $configurableType; + + /** + * @param Configurable $configurableType + */ + public function __construct(Configurable $configurableType) + { + $this->configurableType = $configurableType; + } + + /** + * Add child identities to product identities + * + * @param Product $subject + * @param array $identities + * @return array + */ + public function afterGetIdentities(Product $subject, array $identities): array + { + foreach ($this->configurableType->getChildrenIds($subject->getId()) as $childIds) { + foreach ($childIds as $childId) { + $identities[] = Product::CACHE_TAG . '_' . $childId; + } + } + + return array_unique($identities); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index 81e2e99bfe93a..b7bbf7aa1871c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -12,6 +12,11 @@ use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query\BaseFinalPrice; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\ObjectManager; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogInventory\Model\Configuration; /** * Configurable Products Price Indexer Resource model @@ -65,6 +70,11 @@ class Configurable implements DimensionalIndexerInterface */ private $basePriceModifier; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param BaseFinalPrice $baseFinalPrice * @param IndexTableStructureFactory $indexTableStructureFactory @@ -74,6 +84,7 @@ class Configurable implements DimensionalIndexerInterface * @param BasePriceModifier $basePriceModifier * @param bool $fullReindexAction * @param string $connectionName + * @param ScopeConfigInterface $scopeConfig */ public function __construct( BaseFinalPrice $baseFinalPrice, @@ -83,7 +94,8 @@ public function __construct( \Magento\Framework\App\ResourceConnection $resource, BasePriceModifier $basePriceModifier, $fullReindexAction = false, - $connectionName = 'indexer' + $connectionName = 'indexer', + ScopeConfigInterface $scopeConfig = null ) { $this->baseFinalPrice = $baseFinalPrice; $this->indexTableStructureFactory = $indexTableStructureFactory; @@ -93,10 +105,11 @@ public function __construct( $this->resource = $resource; $this->fullReindexAction = $fullReindexAction; $this->basePriceModifier = $basePriceModifier; + $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** - * {@inheritdoc} + * @inheritdoc * * @throws \Exception */ @@ -184,7 +197,19 @@ private function fillTemporaryOptionsTable(string $temporaryOptionsTableName, ar ['le' => $this->getTable('catalog_product_entity')], 'le.' . $linkField . ' = l.parent_id', [] - )->columns( + ); + + // Does not make sense to extend query if out of stock products won't appear in tables for indexing + if ($this->isConfigShowOutOfStock()) { + $select->join( + ['si' => $this->getTable('cataloginventory_stock_item')], + 'si.product_id = l.product_id', + [] + ); + $select->where('si.is_in_stock = ?', Stock::STOCK_IN_STOCK); + } + + $select->columns( [ 'le.entity_id', 'customer_group_id', @@ -250,7 +275,7 @@ private function getMainTable($dimensions) /** * Get connection * - * return \Magento\Framework\DB\Adapter\AdapterInterface + * @return \Magento\Framework\DB\Adapter\AdapterInterface * @throws \DomainException */ private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface @@ -272,4 +297,17 @@ private function getTable($tableName) { return $this->resource->getTableName($tableName, $this->connectionName); } + + /** + * Is flag Show Out Of Stock setted + * + * @return bool + */ + private function isConfigShowOutOfStock(): bool + { + return $this->scopeConfig->isSetFlag( + Configuration::XML_PATH_SHOW_OUT_OF_STOCK, + ScopeInterface::SCOPE_STORE + ); + } } diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php index df8782ae422b4..c828c0929b40c 100644 --- a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php @@ -5,7 +5,7 @@ */ namespace Magento\ConfigurableProduct\Plugin\Catalog\Model\Product\Pricing\Renderer; -use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as TypeConfigurable; /** * A plugin for a salable resolver. @@ -13,17 +13,16 @@ class SalableResolver { /** - * @var LowestPriceOptionsProviderInterface + * @var TypeConfigurable */ - private $lowestPriceOptionsProvider; + private $typeConfigurable; /** - * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider + * @param TypeConfigurable $typeConfigurable */ - public function __construct( - LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider - ) { - $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider; + public function __construct(TypeConfigurable $typeConfigurable) + { + $this->typeConfigurable = $typeConfigurable; } /** @@ -32,9 +31,7 @@ public function __construct( * @param \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver $subject * @param bool $result * @param \Magento\Framework\Pricing\SaleableInterface $salableItem - * * @return bool - * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterIsSalable( @@ -42,8 +39,8 @@ public function afterIsSalable( $result, \Magento\Framework\Pricing\SaleableInterface $salableItem ) { - if ($salableItem->getTypeId() == 'configurable' && $result) { - $result = $salableItem->isSalable(); + if ($salableItem->getTypeId() === TypeConfigurable::TYPE_CODE && $result) { + $result = $this->typeConfigurable->isSalable($salableItem); } return $result; diff --git a/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php new file mode 100644 index 0000000000000..1ed4432347b7a --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Class Product + * + * @package Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition + */ +class Product +{ + /** + * Prepare configurable product for validation. + * + * @param \Magento\SalesRule\Model\Rule\Condition\Product $subject + * @param \Magento\Framework\Model\AbstractModel $model + * @return array + */ + public function beforeValidate( + \Magento\SalesRule\Model\Rule\Condition\Product $subject, + \Magento\Framework\Model\AbstractModel $model + ) { + $product = $this->getProductToValidate($subject, $model); + if ($model->getProduct() !== $product) { + // We need to replace product only for validation and keep original product for all other cases. + $clone = clone $model; + $clone->setProduct($product); + $model = $clone; + } + + return [$model]; + } + + /** + * Select proper product for validation. + * + * @param \Magento\SalesRule\Model\Rule\Condition\Product $subject + * @param \Magento\Framework\Model\AbstractModel $model + * + * @return \Magento\Catalog\Api\Data\ProductInterface|\Magento\Catalog\Model\Product + */ + private function getProductToValidate( + \Magento\SalesRule\Model\Rule\Condition\Product $subject, + \Magento\Framework\Model\AbstractModel $model + ) { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $model->getProduct(); + + $attrCode = $subject->getAttribute(); + + /* Check for attributes which are not available for configurable products */ + if ($product->getTypeId() == Configurable::TYPE_CODE && !$product->hasData($attrCode)) { + /** @var \Magento\Catalog\Model\AbstractModel $childProduct */ + $childProduct = current($model->getChildren())->getProduct(); + if ($childProduct->hasData($attrCode)) { + $product = $childProduct; + } + } + + return $product; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php index c9a2f7d373a63..1e085e0fdb389 100644 --- a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php +++ b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php @@ -15,8 +15,7 @@ use Magento\ConfigurableProduct\Model\Product\Type\Configurable; /** - * Class UpdateManufacturerAttribute - * @package Magento\ConfigurableProduct\Setup\Patch + * Update manufacturer attribute if it's presented in system. */ class UpdateManufacturerAttribute implements DataPatchInterface, PatchVersionInterface { @@ -31,7 +30,6 @@ class UpdateManufacturerAttribute implements DataPatchInterface, PatchVersionInt private $eavSetupFactory; /** - * UpdateTierPriceAttribute constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param EavSetupFactory $eavSetupFactory */ @@ -44,30 +42,37 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); - $relatedProductTypes = explode( - ',', - $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'manufacturer', 'apply_to') - ); - if (!in_array(Configurable::TYPE_CODE, $relatedProductTypes)) { - $relatedProductTypes[] = Configurable::TYPE_CODE; - $eavSetup->updateAttribute( - \Magento\Catalog\Model\Product::ENTITY, - 'manufacturer', - 'apply_to', - implode(',', $relatedProductTypes) + if ($manufacturer = $eavSetup->getAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'manufacturer', + 'apply_to' + )) { + $relatedProductTypes = explode( + ',', + $manufacturer ); + + if (!in_array(Configurable::TYPE_CODE, $relatedProductTypes)) { + $relatedProductTypes[] = Configurable::TYPE_CODE; + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'manufacturer', + 'apply_to', + implode(',', $relatedProductTypes) + ); + } } } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -77,7 +82,7 @@ public static function getDependencies() } /** - * {@inheritdoc}\ + * @inheritdoc */ public static function getVersion() { @@ -85,7 +90,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index 9ebadc236bb31..0ac1914040d25 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -105,7 +105,7 @@ <seeInTitle userInput="{{product.name}}" stepKey="seeProductNameInTitle"/> </actionGroup> - <actionGroup name="createConfigurationsForAttribute"> + <actionGroup name="generateConfigurationsByAttributeCode"> <arguments> <argument name="attributeCode" type="string" defaultValue="SomeString"/> </arguments> @@ -121,6 +121,12 @@ <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="99" stepKey="enterAttributeQuantity"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + </actionGroup> + + <actionGroup name="createConfigurationsForAttribute" extends="generateConfigurationsByAttributeCode"> + <arguments> + <argument name="attributeCode" type="string" defaultValue="SomeString"/> + </arguments> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> </actionGroup> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index 0de7bc00044c8..f2caef1717e84 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -22,6 +22,8 @@ <element name="removeProductBtn" type="button" selector="//a[text()='Remove Product']"/> <element name="disableProductBtn" type="button" selector="//a[text()='Disable Product']"/> <element name="enableProductBtn" type="button" selector="//a[text()='Enable Product']"/> + <element name="variationsSkuInputByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) input[name*='sku']" type="input" parameterized="true"/> + <element name="variationsSkuInputErrorByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) .admin__field-error" type="text" parameterized="true"/> </section> <section name="AdminConfigurableProductFormSection"> <element name="productWeight" type="input" selector=".admin__control-text[name='product[weight]']"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml new file mode 100644 index 0000000000000..c599a6a23f190 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminConfigurableProductLongSkuTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Create configurable product"/> + <title value="Admin is able to create an product with a long sku below that is below the character limit"/> + <description value="Try to create a product with sku slightly less than char limit. Get client side SKU length error for child products. Correct SKUs and save product succeeds."/> + <severity value="MAJOR"/> + <testCaseId value="MC-5685"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <!--Create product attribute with options--> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <!--Create Category--> + <createData entity="ApiCategory" stepKey="createCategory"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + </before> + + <after> + <!--Clean up products--> + <actionGroup ref="deleteProductByName" stepKey="cleanUpProducts"> + <argument name="sku" value="{{ProductWithLongNameSku.sku}}"/> + <argument name="name" value="{{ProductWithLongNameSku.name}}"/> + </actionGroup> + <!--Clean up attribute--> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <!--Clean up category--> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!--Create a configurable product with long name and sku--> + <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'configurable')}}" stepKey="goToProductCreatePage"/> + <waitForPageLoad stepKey="waitForProductCreatePage"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{ProductWithLongNameSku.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{ProductWithLongNameSku.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{ProductWithLongNameSku.price}}" stepKey="fillProductPrice"/> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="selectCategory"/> + <!--Setup configurations--> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + + <!--See SKU length errors in Current Variations grid--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFail"/> + <seeInCurrentUrl url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'configurable')}}" stepKey="seeRemainOnCreateProductPage"/> + <see selector="{{AdminProductFormConfigurationsSection.variationsSkuInputErrorByRow('1')}}" userInput="Please enter less or equal than 64 symbols." stepKey="seeSkuTooLongError1"/> + <see selector="{{AdminProductFormConfigurationsSection.variationsSkuInputErrorByRow('2')}}" userInput="Please enter less or equal than 64 symbols." stepKey="seeSkuTooLongError2"/> + <!--Fix SKU lengths--> + <fillField selector="{{AdminProductFormConfigurationsSection.variationsSkuInputByRow('1')}}" userInput="LongSku-$$getConfigAttributeOption1.label$$" stepKey="fixConfigurationSku1"/> + <fillField selector="{{AdminProductFormConfigurationsSection.variationsSkuInputByRow('2')}}" userInput="LongSku-$$getConfigAttributeOption2.label$$" stepKey="fixConfigurationSku2"/> + <!--Save product successfully--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductSuccess"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> + + <!--Assert configurations on the product edit pag--> + <seeNumberOfElements selector="{{AdminProductFormConfigurationsSection.currentVariationsRows}}" userInput="2" stepKey="seeNumberOfRows"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsNameCells}}" userInput="{{ProductWithLongNameSku.name}}-$$getConfigAttributeOption1.label$$" stepKey="seeChildProductName1"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsNameCells}}" userInput="{{ProductWithLongNameSku.name}}-$$getConfigAttributeOption2.label$$" stepKey="seeChildProductName2"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" userInput="LongSku-$$getConfigAttributeOption1.label$$" stepKey="seeChildProductSku1"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" userInput="LongSku-$$getConfigAttributeOption2.label$$" stepKey="seeChildProductSku2"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsPriceCells}}" userInput="{{ProductWithLongNameSku.price}}" stepKey="seeConfigurationsPrice"/> + + <!--Assert storefront category list page--> + <amOnPage url="/" stepKey="amOnStorefront"/> + <waitForPageLoad stepKey="waitForStorefrontLoad"/> + <click userInput="$$createCategory.name$$" stepKey="clickOnCategoryName"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see userInput="{{ProductWithLongNameSku.name}}" stepKey="assertProductPresent"/> + <see userInput="{{ProductWithLongNameSku.price}}" stepKey="assertProductPricePresent"/> + + <!--Assert storefront product details page--> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName(ProductWithLongNameSku.name)}}" stepKey="clickOnProductName"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <seeInTitle userInput="{{ProductWithLongNameSku.name}}" stepKey="assertProductNameTitle"/> + <see userInput="{{ProductWithLongNameSku.name}}" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertProductName"/> + <see userInput="{{ProductWithLongNameSku.sku}}" selector="{{StorefrontProductInfoMainSection.productSku}}" stepKey="assertProductSku"/> + <see selector="{{StorefrontProductInfoMainSection.productAttributeTitle1}}" userInput="$$createConfigProductAttribute.default_frontend_label$$" stepKey="seeColorAttributeName1"/> + <see selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" userInput="$$getConfigAttributeOption1.label$$" stepKey="seeInDropDown1"/> + <see selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" userInput="$$getConfigAttributeOption2.label$$" stepKey="seeInDropDown2"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml index 027c2ce729162..001d4d17ec213 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml @@ -228,12 +228,12 @@ <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> - <argument name="product" value="ApiConfigurableProduct"/> + <argument name="product" value="$$createConfigProduct$$"/> </actionGroup> <waitForPageLoad stepKey="waitForProductFilterLoad"/> - - <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage"/> - <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProduct"> + <argument name="product" value="$$createConfigProduct$$" /> + </actionGroup> <!-- Open the wizard for editing configurations and fill out a new attribute --> <click stepKey="clickEditConfig" selector="{{AdminProductFormConfigurationsSection.createConfigurations}}"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml new file mode 100644 index 0000000000000..1959551f8de2d --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableProductCategoryViewChildOnlyTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="View configurable product child in storefront"/> + <title value="It should be possible to only view the child product of a configurable product"/> + <description value="Create configurable product, add to category such that only child variation is visible in category"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-5832"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <!-- Create the category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiCategory" stepKey="secondCategory"/> + + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute we just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute we created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute we created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the configurable product and add it to the category --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + + <!-- Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="logout"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> + <deleteData createDataKey="secondCategory" stepKey="deleteSecondCategory"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + + <!-- Go to the product page for the first product --> + <amOnPage stepKey="goToProductGrid" url="{{ProductCatalogPage.url}}"/> + <waitForPageLoad stepKey="waitForProductGridLoad"/> + <actionGroup stepKey="searchForSimpleProduct" ref="filterProductGridBySku2"> + <argument name="sku" value="$$createConfigChildProduct1.sku$$"/> + </actionGroup> + <actionGroup stepKey="openProductEditPage" ref="openProducForEditByClickingRowXColumnYInProductGrid"/> + <!-- Edit the visibility the first simple product --> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="Catalog, Search" stepKey="selectVisibilityCatalogSearch"/> + <!--Add to category--> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$secondCategory.name$$]" stepKey="addProductToCategory"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveMessageSuccess"/> + + <!-- Go to storefront to view child product --> + <amOnPage stepKey="goToStoreFront" url="{{StorefrontHomePage.url}}"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$secondCategory.name$$)}}" stepKey="goToCategoryStorefront"/> + <waitForPageLoad stepKey="waitForStorefrontCategory"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createConfigChildProduct1.name$$)}}" stepKey="seeChildProductInCategory"/> + <dontSeeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createConfigChildProduct2.name$$)}}" stepKey="dontSeeOtherChildProduct"/> + <dontSeeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createConfigProduct.name$$)}}" stepKey="dontSeeParentProduct"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createConfigChildProduct1.name$$)}}" stepKey="clickProductName"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <seeInCurrentUrl url="$$createConfigChildProduct1.custom_attributes[url_key]$$" stepKey="seeProductPageIsAccessible"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productName($$createConfigChildProduct1.name$$)}}" stepKey="seeProductNameOnProductPage"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml index c93b216fc89d5..57c45ee1e8997 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -16,6 +16,9 @@ <description value="Sort by price should be correct if the apply Catalog Rule to child product of configurable product"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-69988"/> + <skip> + <issueId value="MC-5777"/> + </skip> <group value="сonfigurable_product"/> </annotations> <before> @@ -150,9 +153,9 @@ <argument name="sortBy" value="price"/> <argument name="sort" value="desc"/> </actionGroup> - <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> <!-- Delete the rule --> <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php new file mode 100644 index 0000000000000..b4fb5ccfaa558 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Test\Unit\Model\Plugin\Frontend; + +use Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; + +/** + * Class ProductIdentitiesExtenderTest + */ +class ProductIdentitiesExtenderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Configurable + */ + private $configurableTypeMock; + + /** + * @var ProductIdentitiesExtender + */ + private $plugin; + + /** @var MockObject|\Magento\Catalog\Model\Product */ + private $product; + + protected function setUp() + { + $this->product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMock(); + + $this->configurableTypeMock = $this->getMockBuilder(Configurable::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new ProductIdentitiesExtender($this->configurableTypeMock); + } + + public function testAfterGetIdentities() + { + $identities = [ + 'SomeCacheId', + 'AnotherCacheId', + ]; + $productId = 12345; + $childIdentities = [ + 0 => [1, 2, 5, 100500] + ]; + $expectedIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + Product::CACHE_TAG . '_' . 1, + Product::CACHE_TAG . '_' . 2, + Product::CACHE_TAG . '_' . 5, + Product::CACHE_TAG . '_' . 100500, + ]; + + $this->product->expects($this->once()) + ->method('getId') + ->willReturn($productId); + + $this->configurableTypeMock->expects($this->once()) + ->method('getChildrenIds') + ->with($productId) + ->willReturn($childIdentities); + + $productIdentities = $this->plugin->afterGetIdentities($this->product, $identities); + $this->assertEquals($expectedIdentities, $productIdentities); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index d1cf77f03a7bd..c351d12fa813d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -11,10 +11,13 @@ use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface; use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute; use Magento\ConfigurableProduct\Model\Product\Type\Configurable\AttributeFactory; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\CollectionFactory; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ProductCollection; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory + as ProductCollectionFactory; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\ConfigurableFactory; use Magento\Customer\Model\Session; use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; @@ -27,7 +30,6 @@ * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) - * @codingStandardsIgnoreFile */ class ConfigurableTest extends \PHPUnit\Framework\TestCase { @@ -154,8 +156,7 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->productCollectionFactory = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory::class) + $this->productCollectionFactory = $this->getMockBuilder(ProductCollectionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); @@ -281,8 +282,7 @@ public function testSave() $product->expects($this->atLeastOnce()) ->method('getData') ->willReturnMap($dataMap); - $attribute = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['addData', 'setStoreId', 'setProductId', 'save', '__wakeup', '__sleep']) ->getMock(); @@ -464,8 +464,7 @@ public function testGetConfigurableAttributesAsArray($productStore) $eavAttribute->expects($this->once())->method('getSource')->willReturn($attributeSource); $eavAttribute->expects($this->atLeastOnce())->method('getStoreLabel')->willReturn('Store Label'); - $attribute = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['getProductAttribute', '__wakeup', '__sleep']) ->getMock(); @@ -524,7 +523,7 @@ public function testGetConfigurableAttributesNewProduct() $this->assertEquals([], $this->model->getConfigurableAttributes($product)); } - public function testGetConfigurableAttributes() + public function testGetConfigurableAttributes() { $configurableAttributes = '_cache_instance_configurable_attributes'; @@ -591,8 +590,7 @@ public function testHasOptionsConfigurableAttribute() ->setMethods(['__wakeup', 'getAttributeCode', 'getOptions', 'hasData', 'getData']) ->disableOriginalConstructor() ->getMock(); - $attributeMock = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class) + $attributeMock = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMock(); @@ -698,7 +696,7 @@ function ($value) { ->disableOriginalConstructor() ->getMock(); $usedAttributeMock = $this->getMockBuilder( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class + Attribute::class ) ->setMethods(['getProductAttribute']) ->disableOriginalConstructor() diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php new file mode 100644 index 0000000000000..7ec2ce370ac5d --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Unit\Plugin\Catalog\Model\Product\Pricing\Renderer; + +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as TypeConfigurable; +use Magento\ConfigurableProduct\Plugin\Catalog\Model\Product\Pricing\Renderer\SalableResolver as SalableResolverPlugin; +use Magento\Framework\Pricing\SaleableInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class SalableResolverTest extends TestCase +{ + /** + * @var TypeConfigurable|MockObject + */ + private $typeConfigurable; + + /** + * @var SalableResolverPlugin + */ + private $salableResolver; + + protected function setUp() + { + $this->typeConfigurable = $this->createMock(TypeConfigurable::class); + $this->salableResolver = new SalableResolverPlugin($this->typeConfigurable); + } + + /** + * @param SaleableInterface|MockObject $salableItem + * @param bool $isSalable + * @param bool $typeIsSalable + * @param bool $expectedResult + * @return void + * @dataProvider afterIsSalableDataProvider + */ + public function testAfterIsSalable($salableItem, bool $isSalable, bool $typeIsSalable, bool $expectedResult): void + { + $salableResolver = $this->createMock(SalableResolver::class); + + $this->typeConfigurable->method('isSalable') + ->willReturn($typeIsSalable); + + $result = $this->salableResolver->afterIsSalable($salableResolver, $isSalable, $salableItem); + $this->assertEquals($expectedResult, $result); + } + + /** + * @return array + */ + public function afterIsSalableDataProvider(): array + { + $simpleSalableItem = $this->createMock(SaleableInterface::class); + $simpleSalableItem->expects($this->once()) + ->method('getTypeId') + ->willReturn('simple'); + + $configurableSalableItem = $this->createMock(SaleableInterface::class); + $configurableSalableItem->expects($this->once()) + ->method('getTypeId') + ->willReturn('configurable'); + + return [ + [ + $simpleSalableItem, + true, + false, + true, + ], + [ + $configurableSalableItem, + true, + false, + false, + ], + ]; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php new file mode 100644 index 0000000000000..80979148c4959 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php @@ -0,0 +1,226 @@ +<?php declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Test\Unit\Plugin\SalesRule\Model\Rule\Condition; + +use Magento\Backend\Helper\Data; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ResourceModel\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product as ValidatorPlugin; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\AbstractEntity; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Locale\Format; +use Magento\Framework\Locale\FormatInterface; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Quote\Model\Quote\Item\AbstractItem; +use Magento\Rule\Model\Condition\Context; +use Magento\SalesRule\Model\Rule\Condition\Product as SalesRuleProduct; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.LongVariable) + */ +class ProductTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var SalesRuleProduct + */ + private $validator; + + /** + * @var \Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product + */ + private $validatorPlugin; + + public function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->validator = $this->createValidator(); + $this->validatorPlugin = $this->objectManager->getObject(ValidatorPlugin::class); + } + + /** + * @return \Magento\SalesRule\Model\Rule\Condition\Product + */ + private function createValidator(): SalesRuleProduct + { + /** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */ + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var Data|\PHPUnit_Framework_MockObject_MockObject $backendHelperMock */ + $backendHelperMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var Config|\PHPUnit_Framework_MockObject_MockObject $configMock */ + $configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject $productFactoryMock */ + $productFactoryMock = $this->getMockBuilder(ProductFactory::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $productRepositoryMock */ + $productRepositoryMock = $this->getMockBuilder(ProductRepositoryInterface::class) + ->getMockForAbstractClass(); + $attributeLoaderInterfaceMock = $this->getMockBuilder(AbstractEntity::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributesByCode']) + ->getMock(); + $attributeLoaderInterfaceMock + ->expects($this->any()) + ->method('getAttributesByCode') + ->willReturn([]); + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $productMock */ + $productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['loadAllAttributes', 'getConnection', 'getTable']) + ->getMock(); + $productMock->expects($this->any()) + ->method('loadAllAttributes') + ->willReturn($attributeLoaderInterfaceMock); + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var FormatInterface|\PHPUnit_Framework_MockObject_MockObject $formatMock */ + $formatMock = new Format( + $this->getMockBuilder(ScopeResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(ResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(CurrencyFactory::class)->disableOriginalConstructor()->getMock() + ); + + return new SalesRuleProduct( + $contextMock, + $backendHelperMock, + $configMock, + $productFactoryMock, + $productRepositoryMock, + $productMock, + $collectionMock, + $formatMock + ); + } + + public function testChildIsUsedForValidation() + { + $configurableProductMock = $this->createProductMock(); + $configurableProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Configurable::TYPE_CODE); + $configurableProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(false); + + /* @var AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + $item = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['setProduct', 'getProduct', 'getChildren']) + ->getMockForAbstractClass(); + $item->expects($this->any()) + ->method('getProduct') + ->willReturn($configurableProductMock); + + $simpleProductMock = $this->createProductMock(); + $simpleProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Type::TYPE_SIMPLE); + $simpleProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(true); + + $childItem = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMockForAbstractClass(); + $childItem->expects($this->any()) + ->method('getProduct') + ->willReturn($simpleProductMock); + + $item->expects($this->any()) + ->method('getChildren') + ->willReturn([$childItem]); + $item->expects($this->once()) + ->method('setProduct') + ->with($simpleProductMock); + + $this->validator->setAttribute('special_price'); + + $this->validatorPlugin->beforeValidate($this->validator, $item); + } + + /** + * @return Product|\PHPUnit_Framework_MockObject_MockObject + */ + private function createProductMock(): \PHPUnit_Framework_MockObject_MockObject + { + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getAttribute', + 'getId', + 'setQuoteItemQty', + 'setQuoteItemPrice', + 'getTypeId', + 'hasData', + ]) + ->getMock(); + $productMock + ->expects($this->any()) + ->method('setQuoteItemQty') + ->willReturnSelf(); + $productMock + ->expects($this->any()) + ->method('setQuoteItemPrice') + ->willReturnSelf(); + + return $productMock; + } + + public function testChildIsNotUsedForValidation() + { + $simpleProductMock = $this->createProductMock(); + $simpleProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Type::TYPE_SIMPLE); + $simpleProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(true); + + /* @var AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + $item = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['setProduct', 'getProduct']) + ->getMockForAbstractClass(); + $item->expects($this->any()) + ->method('getProduct') + ->willReturn($simpleProductMock); + + $this->validator->setAttribute('special_price'); + + $this->validatorPlugin->beforeValidate($this->validator, $item); + } +} diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 102ed1314f864..0ae9ffde66f43 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -242,4 +242,7 @@ </argument> </arguments> </type> + <type name="Magento\SalesRule\Model\Rule\Condition\Product"> + <plugin name="apply_rule_on_configurable_children" type="Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product" /> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index bb830c36b929d..df96829b354c8 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -10,4 +10,7 @@ <type name="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface"> <plugin name="Magento_ConfigurableProduct_Plugin_Model_ResourceModel_Attribute_InStockOptionSelectBuilder" type="Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Attribute\InStockOptionSelectBuilder"/> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="product_identities_extender" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender" /> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index a46943bd5d145..8d27e3dc58a4a 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -390,19 +390,23 @@ define([ this.attributeSetHandlerModal().openModal(); } else { this.formElement().save(arguments[0], arguments[1]); + + if (this.formElement().source.get('params.invalid')) { + this.unserializeData(); + } } }, /** * Serialize data for specific form fields * - * Get data from outdated fields, serialize it and produce new form fields. + * Serializes some complex data fields * - * Outdated fields: + * Original fields: * - configurable-matrix; * - associated_product_ids. * - * New fields: + * Serialized fields in request: * - configurable-matrix-serialized; * - associated_product_ids_serialized. */ @@ -420,6 +424,25 @@ define([ } }, + /** + * Unserialize data for specific form fields + * + * Unserializes some fields that were serialized this.serializeData + */ + unserializeData: function () { + if (this.source.data['configurable-matrix-serialized']) { + this.source.data['configurable-matrix'] = + JSON.parse(this.source.data['configurable-matrix-serialized']); + delete this.source.data['configurable-matrix-serialized']; + } + + if (this.source.data['associated_product_ids_serialized']) { + this.source.data['associated_product_ids'] = + JSON.parse(this.source.data['associated_product_ids_serialized']); + delete this.source.data['associated_product_ids_serialized']; + } + }, + /** * Check for newly added attributes * @returns {Boolean} @@ -443,20 +466,20 @@ define([ * @returns {Boolean} */ addNewAttributeSetHandler: function () { - var choosenAttributeSetOption; + var chosenAttributeSetOption; this.formElement().validate(); if (this.formElement().source.get('params.invalid') === false) { - choosenAttributeSetOption = this.attributeSetSelection; + chosenAttributeSetOption = this.attributeSetSelection; - if (choosenAttributeSetOption === 'new') { + if (chosenAttributeSetOption === 'new') { this.createNewAttributeSet(); return false; } - if (choosenAttributeSetOption === 'existing') { + if (chosenAttributeSetOption === 'existing') { this.set( 'skeletonAttributeSet', this.attributeSetId @@ -467,6 +490,10 @@ define([ return true; } + + this.unserializeData(); + + return false; }, /** diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml index 18f96cfaaf398..325ee1d5d79b3 100644 --- a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml +++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml @@ -15,10 +15,13 @@ + '</span>' + '</span>'; %> <li class="item"> - <%= $t('Buy %1 for %2 each and').replace('%1', item.qty).replace('%2', priceStr) %> - <strong class="benefit"> - <%= $t('save') %><span class="percent tier-<%= key %>"> <%= item.percentage %></span>% - </strong> + <%= '<?= $block->escapeHtml(__('Buy %1 for %2 each and', '%1', '%2')) ?>' + .replace('%1', item.qty) + .replace('%2', priceStr) %> + <strong class="benefit"> + <?= $block->escapeHtml(__('save')) ?><span + class="percent tier-<%= key %>"> <%= item.percentage %></span>% + </strong> </li> <% }); %> </ul> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 78974877dd90d..6357bbd6c7c0c 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -291,6 +291,8 @@ define([ images = this.options.spConfig.images[this.simpleProduct]; if (images) { + images = this._sortImages(images); + if (this.options.gallerySwitchStrategy === 'prepend') { images = images.concat(initialImages); } @@ -309,7 +311,17 @@ define([ $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); } - galleryObject.first(); + }, + + /** + * Sorting images array + * + * @private + */ + _sortImages: function (images) { + return _.sortBy(images, function (image) { + return image.position; + }); }, /** diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less new file mode 100644 index 0000000000000..0aaec05aa2afe --- /dev/null +++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less @@ -0,0 +1,42 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +*/ + +& when (@media-common = true) { + .contact-index-index { + .column:not(.sidebar-main) { + .form.contact { + float: none; + width: 50%; + } + } + + .column:not(.sidebar-additional) { + .form.contact { + float: none; + width: 50%; + } + } + } +} + +// Mobile +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .contact-index-index { + .column:not(.sidebar-main) { + .form.contact { + float: none; + width: 100%; + } + } + + .column:not(.sidebar-additional) { + .form.contact { + float: none; + width: 100%; + } + } + } +} + diff --git a/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php b/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php index 2133ae5a323b4..ca9bf4dc7afd6 100644 --- a/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php +++ b/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php @@ -51,7 +51,7 @@ public function getById($customerId); * Retrieve customers which match a specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Customer/Api/GroupRepositoryInterface.php b/app/code/Magento/Customer/Api/GroupRepositoryInterface.php index 2f5e637a7693f..f6ba387e913b2 100644 --- a/app/code/Magento/Customer/Api/GroupRepositoryInterface.php +++ b/app/code/Magento/Customer/Api/GroupRepositoryInterface.php @@ -42,7 +42,7 @@ public function getById($id); * be filtered by tax class. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#GroupRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#GroupRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Customer/Block/Address/Book.php b/app/code/Magento/Customer/Block/Address/Book.php index 8b38946a063db..04669446ffee9 100644 --- a/app/code/Magento/Customer/Block/Address/Book.php +++ b/app/code/Magento/Customer/Block/Address/Book.php @@ -6,8 +6,8 @@ namespace Magento\Customer\Block\Address; use Magento\Customer\Api\AddressRepositoryInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\Address\Mapper; +use Magento\Customer\Block\Address\Grid as AddressesGrid; /** * Customer address book block @@ -24,7 +24,7 @@ class Book extends \Magento\Framework\View\Element\Template protected $currentCustomer; /** - * @var CustomerRepositoryInterface + * @var \Magento\Customer\Api\CustomerRepositoryInterface */ protected $customerRepository; @@ -43,33 +43,44 @@ class Book extends \Magento\Framework\View\Element\Template */ protected $addressMapper; + /** + * @var AddressesGrid + */ + private $addressesGrid; + /** * @param \Magento\Framework\View\Element\Template\Context $context - * @param CustomerRepositoryInterface $customerRepository + * @param CustomerRepositoryInterface|null $customerRepository * @param AddressRepositoryInterface $addressRepository * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer * @param \Magento\Customer\Model\Address\Config $addressConfig * @param Mapper $addressMapper * @param array $data + * @param AddressesGrid|null $addressesGrid + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, - CustomerRepositoryInterface $customerRepository, + \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository = null, AddressRepositoryInterface $addressRepository, \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, \Magento\Customer\Model\Address\Config $addressConfig, Mapper $addressMapper, - array $data = [] + array $data = [], + Grid $addressesGrid = null ) { - $this->customerRepository = $customerRepository; $this->currentCustomer = $currentCustomer; $this->addressRepository = $addressRepository; $this->_addressConfig = $addressConfig; $this->addressMapper = $addressMapper; + $this->addressesGrid = $addressesGrid ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(AddressesGrid::class); parent::__construct($context, $data); } /** + * Prepare the Address Book section layout + * * @return $this */ protected function _prepareLayout() @@ -79,14 +90,20 @@ protected function _prepareLayout() } /** + * Generate and return "New Address" URL + * * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAddAddressUrl */ public function getAddAddressUrl() { - return $this->getUrl('customer/address/new', ['_secure' => true]); + return $this->addressesGrid->getAddAddressUrl(); } /** + * Generate and return "Back" URL + * * @return string */ public function getBackUrl() @@ -98,24 +115,37 @@ public function getBackUrl() } /** + * Generate and return "Delete" URL + * * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getDeleteUrl */ public function getDeleteUrl() { - return $this->getUrl('customer/address/delete'); + return $this->addressesGrid->getDeleteUrl(); } /** + * Generate and return "Edit Address" URL. + * + * Address ID passed in parameters + * * @param int $addressId * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAddressEditUrl */ public function getAddressEditUrl($addressId) { - return $this->getUrl('customer/address/edit', ['_secure' => true, 'id' => $addressId]); + return $this->addressesGrid->getAddressEditUrl($addressId); } /** + * Determines is the address primary (billing or shipping) + * * @return bool + * @throws \Magento\Framework\Exception\LocalizedException */ public function hasPrimaryAddress() { @@ -123,22 +153,22 @@ public function hasPrimaryAddress() } /** + * Get current additional customer addresses + * + * Will return array of address interfaces if customer have additional addresses and false in other case. + * * @return \Magento\Customer\Api\Data\AddressInterface[]|bool + * @throws \Magento\Framework\Exception\LocalizedException + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAdditionalAddresses */ public function getAdditionalAddresses() { try { - $addresses = $this->customerRepository->getById($this->currentCustomer->getCustomerId())->getAddresses(); + $addresses = $this->addressesGrid->getAdditionalAddresses(); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - return false; - } - $primaryAddressIds = [$this->getDefaultBilling(), $this->getDefaultShipping()]; - foreach ($addresses as $address) { - if (!in_array($address->getId(), $primaryAddressIds)) { - $additional[] = $address; - } } - return empty($additional) ? false : $additional; + return empty($addresses) ? false : $addresses; } /** @@ -158,23 +188,23 @@ public function getAddressHtml(\Magento\Customer\Api\Data\AddressInterface $addr } /** + * Get current customer + * * @return \Magento\Customer\Api\Data\CustomerInterface|null */ public function getCustomer() { - $customer = $this->getData('customer'); - if ($customer === null) { - try { - $customer = $this->customerRepository->getById($this->currentCustomer->getCustomerId()); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - return null; - } - $this->setData('customer', $customer); + $customer = null; + try { + $customer = $this->currentCustomer->getCustomer(); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { } return $customer; } /** + * Get customer's default billing address + * * @return int|null */ public function getDefaultBilling() @@ -188,8 +218,11 @@ public function getDefaultBilling() } /** + * Get customer address by ID + * * @param int $addressId * @return \Magento\Customer\Api\Data\AddressInterface|null + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAddressById($addressId) { @@ -201,6 +234,8 @@ public function getAddressById($addressId) } /** + * Get customer's default shipping address + * * @return int|null */ public function getDefaultShipping() diff --git a/app/code/Magento/Customer/Block/Address/Grid.php b/app/code/Magento/Customer/Block/Address/Grid.php new file mode 100644 index 0000000000000..de6767a0ef92a --- /dev/null +++ b/app/code/Magento/Customer/Block/Address/Grid.php @@ -0,0 +1,245 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Address; + +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory as AddressCollectionFactory; +use Magento\Directory\Model\CountryFactory; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Customer address grid + * + * @api + */ +class Grid extends \Magento\Framework\View\Element\Template +{ + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer + */ + private $currentCustomer; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\CollectionFactory + */ + private $addressCollectionFactory; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\Collection + */ + private $addressCollection; + + /** + * @var CountryFactory + */ + private $countryFactory; + + /** + * @param \Magento\Framework\View\Element\Template\Context $context + * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer + * @param AddressCollectionFactory $addressCollectionFactory + * @param CountryFactory $countryFactory + * @param array $data + */ + public function __construct( + \Magento\Framework\View\Element\Template\Context $context, + \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, + AddressCollectionFactory $addressCollectionFactory, + CountryFactory $countryFactory, + array $data = [] + ) { + $this->currentCustomer = $currentCustomer; + $this->addressCollectionFactory = $addressCollectionFactory; + $this->countryFactory = $countryFactory; + + parent::__construct($context, $data); + } + + /** + * Prepare the Address Book section layout + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function _prepareLayout(): void + { + parent::_prepareLayout(); + $this->preparePager(); + } + + /** + * Generate and return "New Address" URL + * + * @return string + */ + public function getAddAddressUrl(): string + { + return $this->getUrl('customer/address/new', ['_secure' => true]); + } + + /** + * Generate and return "Delete" URL + * + * @return string + */ + public function getDeleteUrl(): string + { + return $this->getUrl('customer/address/delete'); + } + + /** + * Generate and return "Edit Address" URL. + * + * Address ID passed in parameters + * + * @param int $addressId + * @return string + */ + public function getAddressEditUrl($addressId): string + { + return $this->getUrl('customer/address/edit', ['_secure' => true, 'id' => $addressId]); + } + + /** + * Get current additional customer addresses + * + * Return array of address interfaces if customer has additional addresses and false in other cases + * + * @return \Magento\Customer\Api\Data\AddressInterface[] + * @throws \Magento\Framework\Exception\LocalizedException + * @throws NoSuchEntityException + */ + public function getAdditionalAddresses(): array + { + $additional = []; + $addresses = $this->getAddressCollection(); + $primaryAddressIds = [$this->getDefaultBilling(), $this->getDefaultShipping()]; + foreach ($addresses as $address) { + if (!in_array((int)$address->getId(), $primaryAddressIds, true)) { + $additional[] = $address->getDataModel(); + } + } + return $additional; + } + + /** + * Get current customer + * + * Return stored customer or get it from session + * + * @return \Magento\Customer\Api\Data\CustomerInterface + */ + public function getCustomer(): \Magento\Customer\Api\Data\CustomerInterface + { + $customer = $this->getData('customer'); + if ($customer === null) { + $customer = $this->currentCustomer->getCustomer(); + $this->setData('customer', $customer); + } + return $customer; + } + + /** + * Get one string street address from the Address DTO passed in parameters + * + * @param \Magento\Customer\Api\Data\AddressInterface $address + * @return string + */ + public function getStreetAddress(\Magento\Customer\Api\Data\AddressInterface $address): string + { + $street = $address->getStreet(); + if (is_array($street)) { + $street = implode(', ', $street); + } + return $street; + } + + /** + * Get country name by $countryCode + * + * Using \Magento\Directory\Model\Country to get country name by $countryCode + * + * @param string $countryCode + * @return string + */ + public function getCountryByCode(string $countryCode): string + { + /** @var \Magento\Directory\Model\Country $country */ + $country = $this->countryFactory->create(); + return $country->loadByCode($countryCode)->getName(); + } + + /** + * Get default billing address + * + * Return address string if address found and null if not + * + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getDefaultBilling(): int + { + $customer = $this->getCustomer(); + + return (int)$customer->getDefaultBilling(); + } + + /** + * Get default shipping address + * + * Return address string if address found and null if not + * + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getDefaultShipping(): int + { + $customer = $this->getCustomer(); + + return (int)$customer->getDefaultShipping(); + } + + /** + * Get pager layout + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function preparePager(): void + { + $addressCollection = $this->getAddressCollection(); + if (null !== $addressCollection) { + $pager = $this->getLayout()->createBlock( + \Magento\Theme\Block\Html\Pager::class, + 'customer.addresses.pager' + )->setCollection($addressCollection); + $this->setChild('pager', $pager); + } + } + + /** + * Get customer addresses collection. + * + * Filters collection by customer id + * + * @return \Magento\Customer\Model\ResourceModel\Address\Collection + * @throws NoSuchEntityException + */ + private function getAddressCollection(): \Magento\Customer\Model\ResourceModel\Address\Collection + { + if (null === $this->addressCollection) { + if (null === $this->getCustomer()) { + throw new NoSuchEntityException(__('Customer not logged in')); + } + /** @var \Magento\Customer\Model\ResourceModel\Address\Collection $collection */ + $collection = $this->addressCollectionFactory->create(); + $collection->setOrder('entity_id', 'desc') + ->setCustomerFilter([$this->getCustomer()->getId()]); + $this->addressCollection = $collection; + } + return $this->addressCollection; + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/CancelButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/CancelButton.php new file mode 100644 index 0000000000000..d94b956918370 --- /dev/null +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/CancelButton.php @@ -0,0 +1,42 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Adminhtml\Edit\Address; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Customer\Block\Adminhtml\Edit\GenericButton; + +/** + * Class CancelButton + */ +class CancelButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @inheritdoc + * + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Cancel'), + 'on_click' => '', + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'customer_form.areas.address.address.customer_address_update_modal', + 'actionName' => 'closeModal' + ], + ], + ], + ], + ], + 'sort_order' => 20 + ]; + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/DeleteButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/DeleteButton.php new file mode 100644 index 0000000000000..da589a25df28e --- /dev/null +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/DeleteButton.php @@ -0,0 +1,65 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Adminhtml\Edit\Address; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Customer\Ui\Component\Listing\Address\Column\Actions; + +/** + * Delete button on edit customer address form + */ +class DeleteButton extends GenericButton implements ButtonProviderInterface +{ + /** + * Get delete button data. + * + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getButtonData() + { + $data = []; + if ($this->getAddressId()) { + $data = [ + 'label' => __('Delete'), + 'on_click' => '', + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'customer_address_form.customer_address_form', + 'actionName' => 'deleteAddress', + 'params' => [ + $this->getDeleteUrl(), + ], + + ] + ], + ], + ], + ], + 'sort_order' => 20 + ]; + } + return $data; + } + + /** + * Get delete button url. + * + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getDeleteUrl(): string + { + return $this->getUrl( + Actions::CUSTOMER_ADDRESS_PATH_DELETE, + ['parent_id' => $this->getCustomerId(), 'id' => $this->getAddressId()] + ); + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/GenericButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/GenericButton.php new file mode 100644 index 0000000000000..ae09ee6896891 --- /dev/null +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/GenericButton.php @@ -0,0 +1,110 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Adminhtml\Edit\Address; + +use Magento\Customer\Model\AddressFactory; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\UrlInterface; +use Magento\Customer\Model\ResourceModel\Address; +use Magento\Customer\Model\ResourceModel\AddressRepository; + +/** + * Class for common code for buttons on the create/edit address form + */ +class GenericButton +{ + /** + * @var AddressFactory + */ + private $addressFactory; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var Address + */ + private $addressResourceModel; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var AddressRepository + */ + private $addressRepository; + + /** + * @param AddressFactory $addressFactory + * @param UrlInterface $urlBuilder + * @param Address $addressResourceModel + * @param RequestInterface $request + * @param AddressRepository $addressRepository + */ + public function __construct( + AddressFactory $addressFactory, + UrlInterface $urlBuilder, + Address $addressResourceModel, + RequestInterface $request, + AddressRepository $addressRepository + ) { + $this->addressFactory = $addressFactory; + $this->urlBuilder = $urlBuilder; + $this->addressResourceModel = $addressResourceModel; + $this->request = $request; + $this->addressRepository = $addressRepository; + } + + /** + * Return address Id. + * + * @return int|null + */ + public function getAddressId() + { + $address = $this->addressFactory->create(); + + $entityId = $this->request->getParam('entity_id'); + $this->addressResourceModel->load( + $address, + $entityId + ); + + return $address->getEntityId() ?: null; + } + + /** + * Get customer id. + * + * @return int|null + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getCustomerId() + { + $addressId = $this->request->getParam('entity_id'); + + $address = $this->addressRepository->getById($addressId); + + return $address->getCustomerId() ?: null; + } + + /** + * Generate url by route and parameters + * + * @param string $route + * @param array $params + * @return string + */ + public function getUrl($route = '', array $params = []): string + { + return $this->urlBuilder->getUrl($route, $params); + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/SaveButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/SaveButton.php new file mode 100644 index 0000000000000..9d403185ca888 --- /dev/null +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Address/SaveButton.php @@ -0,0 +1,34 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Adminhtml\Edit\Address; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Customer\Block\Adminhtml\Edit\GenericButton; + +/** + * Class SaveButton + */ +class SaveButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @inheritdoc + * + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 10 + ]; + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Sales.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Sales.php index 76c33f143e671..5eeacca4c73a5 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Sales.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Sales.php @@ -147,10 +147,12 @@ public function _beforeToHtml() */ public function getWebsiteCount($websiteId) { - return isset($this->_websiteCounts[$websiteId]) ? $this->_websiteCounts[$websiteId] : 0; + return $this->_websiteCounts[$websiteId] ?? 0; } /** + * Returns Grouped Collection Rows + * * @return array */ public function getRows() @@ -159,6 +161,8 @@ public function getRows() } /** + * Return totals data + * * @return \Magento\Framework\DataObject */ public function getTotals() @@ -171,7 +175,9 @@ public function getTotals() * * @param float $price * @param null|int $websiteId + * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function formatCurrency($price, $websiteId = null) { diff --git a/app/code/Magento/Customer/Block/Form/Register.php b/app/code/Magento/Customer/Block/Form/Register.php index f31012a52a98e..322dd2cbfe915 100644 --- a/app/code/Magento/Customer/Block/Form/Register.php +++ b/app/code/Magento/Customer/Block/Form/Register.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Block\Form; use Magento\Customer\Model\AccountManagement; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; /** * Customer register form block @@ -86,6 +87,8 @@ public function getConfig($path) } /** + * Prepare layout + * * @return $this */ protected function _prepareLayout() @@ -177,11 +180,13 @@ public function getRegion() */ public function isNewsletterEnabled() { - return $this->_moduleManager->isOutputEnabled('Magento_Newsletter'); + return $this->_moduleManager->isOutputEnabled('Magento_Newsletter') + && $this->getConfig(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE); } /** * Restore entity data from session + * * Entity and form code must be defined for the form * * @param \Magento\Customer\Model\Metadata\Form $form diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultBillingAddress.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultBillingAddress.php new file mode 100644 index 0000000000000..bf8ca4aeb5cd7 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultBillingAddress.php @@ -0,0 +1,109 @@ +<?php +declare(strict_types=1); +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; +use Psr\Log\LoggerInterface; + +/** + * Class to process set default billing address action + */ +class DefaultBillingAddress extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @param Action\Context $context + * @param AddressRepositoryInterface $addressRepository + * @param LoggerInterface $logger + * @param JsonFactory $resultJsonFactory + */ + public function __construct( + Action\Context $context, + AddressRepositoryInterface $addressRepository, + LoggerInterface $logger, + JsonFactory $resultJsonFactory + ) { + parent::__construct($context); + $this->addressRepository = $addressRepository; + $this->logger = $logger; + $this->resultJsonFactory = $resultJsonFactory; + } + + /** + * Execute action to set customer default billing address + * + * @return Json + */ + public function execute(): Json + { + $customerId = $this->getRequest()->getParam('parent_id', false); + $addressId = $this->getRequest()->getParam('id', false); + $error = true; + $message = __('There is no address id in setting default billing address.'); + + if ($addressId) { + try { + $address = $this->addressRepository->getById($addressId)->setCustomerId($customerId); + $this->setAddressAsDefault($address); + $this->addressRepository->save($address); + $message = __('Default billing address has been changed.'); + $error = false; + } catch (\Exception $e) { + $message = __('We can\'t change default billing address right now.'); + $this->logger->critical($e); + } + } + + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setData( + [ + 'message' => $message, + 'error' => $error, + ] + ); + + return $resultJson; + } + + /** + * Set address as default billing address + * + * @param AddressInterface $address + * @return void + */ + private function setAddressAsDefault(AddressInterface $address): void + { + $address->setIsDefaultBilling(true); + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultShippingAddress.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultShippingAddress.php new file mode 100644 index 0000000000000..81928ae2d28d0 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/DefaultShippingAddress.php @@ -0,0 +1,109 @@ +<?php +declare(strict_types=1); +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; +use Psr\Log\LoggerInterface; + +/** + * Class to process set default shipping address action + */ +class DefaultShippingAddress extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @param Action\Context $context + * @param AddressRepositoryInterface $addressRepository + * @param LoggerInterface $logger + * @param JsonFactory $resultJsonFactory + */ + public function __construct( + Action\Context $context, + AddressRepositoryInterface $addressRepository, + LoggerInterface $logger, + JsonFactory $resultJsonFactory + ) { + parent::__construct($context); + $this->addressRepository = $addressRepository; + $this->logger = $logger; + $this->resultJsonFactory = $resultJsonFactory; + } + + /** + * Execute action to set customer default shipping address + * + * @return Json + */ + public function execute(): Json + { + $customerId = $this->getRequest()->getParam('parent_id', false); + $addressId = $this->getRequest()->getParam('id', false); + $error = true; + $message = __('There is no address id in setting default shipping address.'); + + if ($addressId) { + try { + $address = $this->addressRepository->getById($addressId)->setCustomerId($customerId); + $this->setAddressAsDefault($address); + $this->addressRepository->save($address); + $message = __('Default shipping address has been changed.'); + $error = false; + } catch (\Exception $e) { + $message = __('We can\'t change default shipping address right now.'); + $this->logger->critical($e); + } + } + + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setData( + [ + 'message' => $message, + 'error' => $error, + ] + ); + + return $resultJson; + } + + /** + * Set address as default shipping address + * + * @param AddressInterface $address + * @return void + */ + private function setAddressAsDefault(AddressInterface $address): void + { + $address->setIsDefaultShipping(true); + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/Delete.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/Delete.php new file mode 100644 index 0000000000000..711cd2473cdc5 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/Delete.php @@ -0,0 +1,95 @@ +<?php +declare(strict_types=1); +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Customer\Api\AddressRepositoryInterface; +use Psr\Log\LoggerInterface; + +/** + * Button for deletion of customer address in admin + */ +class Delete extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param Action\Context $context + * @param AddressRepositoryInterface $addressRepository + * @param JsonFactory $resultJsonFactory + * @param LoggerInterface $logger + */ + public function __construct( + Action\Context $context, + AddressRepositoryInterface $addressRepository, + JsonFactory $resultJsonFactory, + LoggerInterface $logger + ) { + parent::__construct($context); + $this->addressRepository = $addressRepository; + $this->resultJsonFactory = $resultJsonFactory; + $this->logger = $logger; + } + + /** + * Delete customer address action + * + * @return Json + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(): Json + { + $customerId = $this->getRequest()->getParam('parent_id', false); + $addressId = $this->getRequest()->getParam('id', false); + $error = false; + $message = ''; + if ($addressId && $this->addressRepository->getById($addressId)->getCustomerId() === $customerId) { + try { + $this->addressRepository->deleteById($addressId); + $message = __('You deleted the address.'); + } catch (\Exception $e) { + $error = true; + $message = __('We can\'t delete the address right now.'); + $this->logger->critical($e); + } + } + + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setData( + [ + 'message' => $message, + 'error' => $error, + ] + ); + + return $resultJson; + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/MassDelete.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/MassDelete.php new file mode 100644 index 0000000000000..cd319d662a093 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/MassDelete.php @@ -0,0 +1,131 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Ui\Component\MassAction\Filter; +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory; +use Magento\Customer\Api\AddressRepositoryInterface; +use Psr\Log\LoggerInterface; + +/** + * Class to delete selected customer addresses through massaction + */ +class MassDelete extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see MassDelete::_isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var Filter + */ + private $filter; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @param Context $context + * @param Filter $filter + * @param CollectionFactory $collectionFactory + * @param AddressRepositoryInterface $addressRepository + * @param LoggerInterface $logger + * @param JsonFactory $resultJsonFactory + */ + public function __construct( + Context $context, + Filter $filter, + CollectionFactory $collectionFactory, + AddressRepositoryInterface $addressRepository, + LoggerInterface $logger, + JsonFactory $resultJsonFactory + ) { + $this->filter = $filter; + $this->collectionFactory = $collectionFactory; + $this->addressRepository = $addressRepository; + $this->logger = $logger; + $this->resultJsonFactory = $resultJsonFactory; + parent::__construct($context); + } + + /** + * Delete specified customer addresses using grid massaction + * + * @return Json + * @throws LocalizedException + */ + public function execute(): Json + { + $customerData = $this->_session->getData('customer_data'); + /** @var \Magento\Customer\Model\ResourceModel\Address\Collection $collection */ + $collection = $this->filter->getCollection($this->collectionFactory->create()); + $error = false; + + try { + if ($customerData && $customerData['customer_id']) { + $collection->addFieldToFilter('parent_id', $customerData['customer_id']); + } else { + throw new \Exception(); + } + $collectionSize = $collection->getSize(); + /** @var \Magento\Customer\Model\Address $address */ + foreach ($collection as $address) { + $this->addressRepository->deleteById($address->getId()); + } + $message = __('A total of %1 record(s) have been deleted.', $collectionSize); + } catch (NoSuchEntityException $e) { + $message = __('There is no such address entity to delete.'); + $error = true; + $this->logger->critical($e); + } catch (LocalizedException $e) { + $message = __($e->getMessage()); + $error = true; + $this->logger->critical($e); + } catch (\Exception $e) { + $message = __('We can\'t mass delete the addresses right now.'); + $error = true; + $this->logger->critical($e); + } + + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setData( + [ + 'message' => $message, + 'error' => $error, + ] + ); + + return $resultJson; + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php new file mode 100644 index 0000000000000..e4daea6f59fdb --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php @@ -0,0 +1,174 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Psr\Log\LoggerInterface; + +/** + * Class for saving of customer address + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Save extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var \Magento\Customer\Api\AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @var \Magento\Customer\Model\Metadata\FormFactory + */ + private $formFactory; + + /** + * @var \Magento\Customer\Api\CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var \Magento\Framework\Api\DataObjectHelper + */ + private $dataObjectHelper; + + /** + * @var \Magento\Customer\Api\Data\AddressInterfaceFactory + */ + private $addressDataFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @param Action\Context $context + * @param \Magento\Customer\Api\AddressRepositoryInterface $addressRepository + * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory + * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository + * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + * @param \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory + * @param LoggerInterface $logger + * @param JsonFactory $resultJsonFactory + */ + public function __construct( + Action\Context $context, + \Magento\Customer\Api\AddressRepositoryInterface $addressRepository, + \Magento\Customer\Model\Metadata\FormFactory $formFactory, + \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory, + LoggerInterface $logger, + JsonFactory $resultJsonFactory + ) { + parent::__construct($context); + $this->addressRepository = $addressRepository; + $this->formFactory = $formFactory; + $this->customerRepository = $customerRepository; + $this->dataObjectHelper = $dataObjectHelper; + $this->addressDataFactory = $addressDataFactory; + $this->logger = $logger; + $this->resultJsonFactory = $resultJsonFactory; + } + + /** + * Save customer address action + * + * @return Json + */ + public function execute(): Json + { + $customerId = $this->getRequest()->getParam('parent_id', false); + $addressId = $this->getRequest()->getParam('entity_id', false); + + $error = false; + try { + /** @var \Magento\Customer\Api\Data\CustomerInterface $customer */ + $customer = $this->customerRepository->getById($customerId); + + $addressForm = $this->formFactory->create( + 'customer_address', + 'adminhtml_customer_address', + [], + false, + false + ); + $addressData = $addressForm->extractData($this->getRequest()); + $addressData = $addressForm->compactData($addressData); + + $addressData['region'] = [ + 'region' => $addressData['region'] ?? null, + 'region_id' => $addressData['region_id'] ?? null, + ]; + $addressToSave = $this->addressDataFactory->create(); + $this->dataObjectHelper->populateWithArray( + $addressToSave, + $addressData, + \Magento\Customer\Api\Data\AddressInterface::class + ); + $addressToSave->setCustomerId($customer->getId()); + $addressToSave->setIsDefaultBilling( + (bool)$this->getRequest()->getParam('default_billing', false) + ); + $addressToSave->setIsDefaultShipping( + (bool)$this->getRequest()->getParam('default_shipping', false) + ); + if ($addressId) { + $addressToSave->setId($addressId); + $message = __('Customer address has been updated.'); + } else { + $addressToSave->setId(null); + $message = __('New customer address has been added.'); + } + $savedAddress = $this->addressRepository->save($addressToSave); + $addressId = $savedAddress->getId(); + } catch (NoSuchEntityException $e) { + $this->logger->critical($e); + $error = true; + $message = __('There is no customer with such id.'); + } catch (LocalizedException $e) { + $error = true; + $message = __($e->getMessage()); + $this->logger->critical($e); + } catch (\Exception $e) { + $error = true; + $message = __('We can\'t change customer address right now.'); + $this->logger->critical($e); + } + + $addressId = empty($addressId) ? null : $addressId; + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setData( + [ + 'message' => $message, + 'error' => $error, + 'data' => [ + 'entity_id' => $addressId + ] + ] + ); + + return $resultJson; + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/Validate.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/Validate.php new file mode 100644 index 0000000000000..16a40fd9016f8 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/Validate.php @@ -0,0 +1,99 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\DataObject; + +/** + * Class for validation of customer address form on admin. + */ +class Validate extends Action implements HttpPostActionInterface, HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + + /** + * @var \Magento\Framework\Controller\Result\JsonFactory + */ + private $resultJsonFactory; + + /** + * @var \Magento\Customer\Model\Metadata\FormFactory + */ + private $formFactory; + + /** + * @param Action\Context $context + * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory + * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory + */ + public function __construct( + Action\Context $context, + \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, + \Magento\Customer\Model\Metadata\FormFactory $formFactory + ) { + parent::__construct($context); + $this->resultJsonFactory = $resultJsonFactory; + $this->formFactory = $formFactory; + } + + /** + * AJAX customer address validation action + * + * @return Json + */ + public function execute(): Json + { + /** @var \Magento\Framework\DataObject $response */ + $response = new \Magento\Framework\DataObject(); + $response->setError(false); + + /** @var \Magento\Framework\DataObject $validatedResponse */ + $validatedResponse = $this->validateCustomerAddress($response); + $resultJson = $this->resultJsonFactory->create(); + if ($validatedResponse->getError()) { + $validatedResponse->setError(true); + $validatedResponse->setMessages($response->getMessages()); + } + + $resultJson->setData($validatedResponse); + + return $resultJson; + } + + /** + * Customer address validation. + * + * @param DataObject $response + * @return \Magento\Framework\DataObject + */ + private function validateCustomerAddress(DataObject $response): DataObject + { + $addressForm = $this->formFactory->create('customer_address', 'adminhtml_customer_address'); + $formData = $addressForm->extractData($this->getRequest()); + + $errors = $addressForm->validateData($formData); + if ($errors !== true) { + $messages = $response->hasMessages() ? $response->getMessages() : []; + foreach ($errors as $error) { + $messages[] = $error; + } + $response->setMessages($messages); + $response->setError(true); + } + + return $response; + } +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/File/Address/Upload.php b/app/code/Magento/Customer/Controller/Adminhtml/File/Address/Upload.php index 506eac3230200..e9034c8050383 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/File/Address/Upload.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/File/Address/Upload.php @@ -10,11 +10,16 @@ use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Model\FileUploader; use Magento\Customer\Model\FileUploaderFactory; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\LocalizedException; use Psr\Log\LoggerInterface; -class Upload extends Action +/** + * Uploads files for customer address + */ +class Upload extends Action implements HttpGetActionInterface, HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -38,21 +43,29 @@ class Upload extends Action */ private $logger; + /** + * @var string + */ + private $scope; + /** * @param Context $context * @param FileUploaderFactory $fileUploaderFactory * @param AddressMetadataInterface $addressMetadataService * @param LoggerInterface $logger + * @param string $scope */ public function __construct( Context $context, FileUploaderFactory $fileUploaderFactory, AddressMetadataInterface $addressMetadataService, - LoggerInterface $logger + LoggerInterface $logger, + string $scope = 'address' ) { $this->fileUploaderFactory = $fileUploaderFactory; $this->addressMetadataService = $addressMetadataService; $this->logger = $logger; + $this->scope = $scope; parent::__construct($context); } @@ -69,14 +82,14 @@ public function execute() // Must be executed before any operations with $_FILES! $this->convertFilesArray(); - $attributeCode = key($_FILES['address']['name']); + $attributeCode = key($_FILES[$this->scope]['name']); $attributeMetadata = $this->addressMetadataService->getAttributeMetadata($attributeCode); /** @var FileUploader $fileUploader */ $fileUploader = $this->fileUploaderFactory->create([ 'attributeMetadata' => $attributeMetadata, 'entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS, - 'scope' => 'address', + 'scope' => $this->scope, ]); $errors = $fileUploader->validate(); @@ -114,14 +127,11 @@ public function execute() */ private function convertFilesArray() { - foreach ($_FILES['address'] as $itemKey => $item) { - foreach ($item as $value) { - if (is_array($value)) { - $_FILES['address'][$itemKey] = [ - key($value) => current($value), - ]; - } + foreach ($_FILES as $itemKey => $item) { + foreach ($item as $fieldName => $value) { + $_FILES[$this->scope][$fieldName] = [$itemKey => $value]; } + unset($_FILES[$itemKey]); } } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 45a7c0182d41c..adb420f983006 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -15,6 +15,8 @@ use Magento\Framework\Exception\LocalizedException; /** + * Save customer action. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface @@ -108,6 +110,7 @@ protected function _extractData( /** * Saves default_billing and default_shipping flags for customer address * + * @deprecated must be removed because addresses are save separately for now * @param array $addressIdList * @param array $extractedCustomerData * @return array @@ -150,6 +153,7 @@ protected function saveDefaultFlags(array $addressIdList, array & $extractedCust /** * Reformat customer addresses data to be compatible with customer service interface * + * @deprecated addresses are saved separately for now * @param array $extractedCustomerData * @return array */ @@ -188,7 +192,6 @@ public function execute() try { // optional fields might be set in request for future processing by observers in other modules $customerData = $this->_extractCustomerData(); - $addressesData = $this->_extractCustomerAddressData($customerData); if ($customerId) { $currentCustomer = $this->_customerRepository->getById($customerId); @@ -206,28 +209,12 @@ public function execute() $customerData, \Magento\Customer\Api\Data\CustomerInterface::class ); - $addresses = []; - foreach ($addressesData as $addressData) { - $region = isset($addressData['region']) ? $addressData['region'] : null; - $regionId = isset($addressData['region_id']) ? $addressData['region_id'] : null; - $addressData['region'] = [ - 'region' => $region, - 'region_id' => $regionId, - ]; - $addressDataObject = $this->addressDataFactory->create(); - $this->dataObjectHelper->populateWithArray( - $addressDataObject, - $addressData, - \Magento\Customer\Api\Data\AddressInterface::class - ); - $addresses[] = $addressDataObject; - } $this->_eventManager->dispatch( 'adminhtml_customer_prepare_save', ['customer' => $customer, 'request' => $this->getRequest()] ); - $customer->setAddresses($addresses); + if (isset($customerData['sendemail_store_id'])) { $customer->setStoreId($customerData['sendemail_store_id']); } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php index be09eb7daff76..d91bc7424bffe 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php @@ -11,6 +11,9 @@ use Magento\Framework\Message\Error; use Magento\Customer\Controller\Adminhtml\Index as CustomerAction; +/** + * Class for validation of customer + */ class Validate extends CustomerAction implements HttpPostActionInterface, HttpGetActionInterface { /** @@ -72,40 +75,6 @@ protected function _validateCustomer($response) return $customer; } - /** - * Customer address validation. - * - * @param \Magento\Framework\DataObject $response - * @return void - */ - protected function _validateCustomerAddress($response) - { - $addresses = $this->getRequest()->getPost('address'); - if (!is_array($addresses)) { - return; - } - foreach (array_keys($addresses) as $index) { - if ($index == '_template_') { - continue; - } - - $addressForm = $this->_formFactory->create('customer_address', 'adminhtml_customer_address'); - - $requestScope = sprintf('address/%s', $index); - $formData = $addressForm->extractData($this->getRequest(), $requestScope); - - $errors = $addressForm->validateData($formData); - if ($errors !== true) { - $messages = $response->hasMessages() ? $response->getMessages() : []; - foreach ($errors as $error) { - $messages[] = $error; - } - $response->setMessages($messages); - $response->setError(1); - } - } - } - /** * AJAX customer validation action * @@ -116,10 +85,7 @@ public function execute() $response = new \Magento\Framework\DataObject(); $response->setError(0); - $customer = $this->_validateCustomer($response); - if ($customer) { - $this->_validateCustomerAddress($response); - } + $this->_validateCustomer($response); $resultJson = $this->resultJsonFactory->create(); if ($response->getError()) { $response->setError(true); diff --git a/app/code/Magento/Customer/Controller/Ajax/Login.php b/app/code/Magento/Customer/Controller/Ajax/Login.php index 6d0f8c036b290..5049c83e60f35 100644 --- a/app/code/Magento/Customer/Controller/Ajax/Login.php +++ b/app/code/Magento/Customer/Controller/Ajax/Login.php @@ -27,9 +27,9 @@ class Login extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface { /** - * @var \Magento\Framework\Session\Generic + * @var \Magento\Customer\Model\Session */ - protected $session; + protected $customerSession; /** * @var AccountManagementInterface @@ -202,17 +202,15 @@ public function execute() $response['redirectUrl'] = $this->_redirect->success($redirectRoute); $this->getAccountRedirect()->clearRedirectCookie(); } - } catch (LocalizedException | InvalidEmailOrPasswordException | EmailNotConfirmedException $e) { + } catch (LocalizedException $e) { $response = [ 'errors' => true, 'message' => $e->getMessage(), - 'captcha' => $this->customerSession->getData('user_login_show_captcha') ]; } catch (\Exception $e) { $response = [ 'errors' => true, 'message' => __('Invalid login or password.'), - 'captcha' => $this->customerSession->getData('user_login_show_captcha') ]; } /** @var \Magento\Framework\Controller\Result\Json $resultJson */ diff --git a/app/code/Magento/Customer/Controller/Ajax/Logout.php b/app/code/Magento/Customer/Controller/Ajax/Logout.php index 0edd41e3632b1..c9eadacd12e65 100644 --- a/app/code/Magento/Customer/Controller/Ajax/Logout.php +++ b/app/code/Magento/Customer/Controller/Ajax/Logout.php @@ -7,18 +7,20 @@ namespace Magento\Customer\Controller\Ajax; +use Magento\Framework\App\Action\HttpGetActionInterface; + /** * Logout controller * * @method \Magento\Framework\App\RequestInterface getRequest() * @method \Magento\Framework\App\Response\Http getResponse() */ -class Logout extends \Magento\Framework\App\Action\Action +class Logout extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** - * @var \Magento\Framework\Session\Generic + * @var \Magento\Customer\Model\Session */ - protected $session; + protected $customerSession; /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 0e2503c837d94..d7c5d7f47a4cf 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -18,6 +18,7 @@ use Magento\Customer\Model\Customer as CustomerModel; use Magento\Customer\Model\Customer\CredentialsValidator; use Magento\Customer\Model\Metadata\Validator; +use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory; use Magento\Eav\Model\Validator\Attribute\Backend; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Api\SearchCriteriaBuilder; @@ -45,14 +46,13 @@ use Magento\Framework\Phrase; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; +use Magento\Framework\Session\SaveHandlerInterface; +use Magento\Framework\Session\SessionManagerInterface; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\StringUtils as StringHelper; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Psr\Log\LoggerInterface as PsrLogger; -use Magento\Framework\Session\SessionManagerInterface; -use Magento\Framework\Session\SaveHandlerInterface; -use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory; /** * Handle various customer account actions @@ -333,6 +333,11 @@ class AccountManagement implements AccountManagementInterface */ private $searchCriteriaBuilder; + /** + * @var AddressRegistry + */ + private $addressRegistry; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -364,7 +369,9 @@ class AccountManagement implements AccountManagementInterface * @param SaveHandlerInterface|null $saveHandler * @param CollectionFactory|null $visitorCollectionFactory * @param SearchCriteriaBuilder|null $searchCriteriaBuilder + * @param AddressRegistry|null $addressRegistry * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function __construct( CustomerFactory $customerFactory, @@ -396,7 +403,8 @@ public function __construct( SessionManagerInterface $sessionManager = null, SaveHandlerInterface $saveHandler = null, CollectionFactory $visitorCollectionFactory = null, - SearchCriteriaBuilder $searchCriteriaBuilder = null + SearchCriteriaBuilder $searchCriteriaBuilder = null, + AddressRegistry $addressRegistry = null ) { $this->customerFactory = $customerFactory; $this->eventManager = $eventManager; @@ -434,6 +442,8 @@ public function __construct( ?: ObjectManager::getInstance()->get(CollectionFactory::class); $this->searchCriteriaBuilder = $searchCriteriaBuilder ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); + $this->addressRegistry = $addressRegistry + ?: ObjectManager::getInstance()->get(AddressRegistry::class); } /** @@ -579,6 +589,9 @@ public function initiatePasswordReset($email, $template, $websiteId = null) // load customer by email $customer = $this->customerRepository->get($email, $websiteId); + // No need to validate customer address while saving customer reset password token + $this->disableAddressValidation($customer); + $newPasswordToken = $this->mathRandom->getUniqueHash(); $this->changeResetPasswordLinkToken($customer, $newPasswordToken); @@ -669,6 +682,10 @@ public function resetPassword($email, $resetToken, $newPassword) } else { $customer = $this->customerRepository->get($email); } + + // No need to validate customer address while saving customer reset password token + $this->disableAddressValidation($customer); + //Validate Token and new password strength $this->validateResetPasswordToken($customer->getId(), $resetToken); $this->credentialsValidator->checkPasswordDifferentFromEmail( @@ -926,6 +943,8 @@ public function getDefaultShippingAddress($customerId) * @param string $redirectUrl * @param array $extensions * @return void + * @throws LocalizedException + * @throws NoSuchEntityException */ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl, $extensions = []) { @@ -987,7 +1006,10 @@ public function changePasswordById($customerId, $currentPassword, $newPassword) * @param string $newPassword * @return bool true on success * @throws InputException + * @throws InputMismatchException * @throws InvalidEmailOrPasswordException + * @throws LocalizedException + * @throws NoSuchEntityException * @throws UserLockedException */ private function changePasswordForCustomer($customer, $currentPassword, $newPassword) @@ -1202,6 +1224,8 @@ protected function sendNewAccountEmail( * * @param CustomerInterface $customer * @return $this + * @throws LocalizedException + * @throws NoSuchEntityException * @deprecated 100.1.0 */ protected function sendPasswordResetNotificationEmail($customer) @@ -1264,6 +1288,7 @@ protected function getTemplateTypes() * @param int|null $storeId * @param string $email * @return $this + * @throws MailException * @deprecated 100.1.0 */ protected function sendEmailTemplate( @@ -1379,6 +1404,9 @@ public function isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt) * @param string $passwordLinkToken * @return bool * @throws InputException + * @throws InputMismatchException + * @throws LocalizedException + * @throws NoSuchEntityException */ public function changeResetPasswordLinkToken($customer, $passwordLinkToken) { @@ -1407,6 +1435,8 @@ public function changeResetPasswordLinkToken($customer, $passwordLinkToken) * * @param CustomerInterface $customer * @return $this + * @throws LocalizedException + * @throws NoSuchEntityException * @deprecated 100.1.0 */ public function sendPasswordReminderEmail($customer) @@ -1434,6 +1464,8 @@ public function sendPasswordReminderEmail($customer) * * @param CustomerInterface $customer * @return $this + * @throws LocalizedException + * @throws NoSuchEntityException * @deprecated 100.1.0 */ public function sendPasswordResetConfirmationEmail($customer) @@ -1478,6 +1510,7 @@ protected function getAddressById(CustomerInterface $customer, $addressId) * * @param CustomerInterface $customer * @return Data\CustomerSecure + * @throws NoSuchEntityException * @deprecated 100.1.0 */ protected function getFullCustomerObject($customer) @@ -1505,6 +1538,20 @@ public function getPasswordHash($password) return $this->encryptor->getHash($password); } + /** + * Disable Customer Address Validation + * + * @param CustomerInterface $customer + * @throws NoSuchEntityException + */ + private function disableAddressValidation($customer) + { + foreach ($customer->getAddresses() as $address) { + $addressModel = $this->addressRegistry->retrieve($address->getId()); + $addressModel->setShouldIgnoreValidation(true); + } + } + /** * Get email notification * diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 1b7e3ca6aead0..146fec4c79f46 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -230,7 +230,7 @@ public function getStreet() public function getStreetLine($number) { $lines = $this->getStreet(); - return isset($lines[$number - 1]) ? $lines[$number - 1] : ''; + return $lines[$number - 1] ?? ''; } /** @@ -271,8 +271,9 @@ public function setStreet($street) * Enforce format of the street field or other multiline custom attributes * * @param array|string $key - * @param mixed $value - * @return $this + * @param array|string|null $value + * + * @return \Magento\Framework\DataObject */ public function setData($key, $value = null) { @@ -404,7 +405,7 @@ public function getRegionCode() } /** - * Get region id + * Return Region ID * * @return int */ @@ -507,7 +508,7 @@ public function getConfig() } /** - * Before save handler + * Processing object before save data * * @return $this */ @@ -523,10 +524,12 @@ public function beforeSave() * * @param int|null $defaultBillingAddressId * @param int|null $defaultShippingAddressId + * * @return AddressInterface * Use Api/Data/AddressInterface as a result of service operations. Don't rely on the model to provide * the instance of Api/Data/AddressInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws \Magento\Framework\Exception\LocalizedException */ public function getDataModel($defaultBillingAddressId = null, $defaultShippingAddressId = null) { @@ -633,6 +636,7 @@ public function unsRegion() * * @return bool * @since 100.2.0 + * @throws \Magento\Framework\Exception\LocalizedException */ protected function isCompanyRequired() { @@ -644,6 +648,7 @@ protected function isCompanyRequired() * * @return bool * @since 100.2.0 + * @throws \Magento\Framework\Exception\LocalizedException */ protected function isTelephoneRequired() { @@ -655,6 +660,7 @@ protected function isTelephoneRequired() * * @return bool * @since 100.2.0 + * @throws \Magento\Framework\Exception\LocalizedException */ protected function isFaxRequired() { diff --git a/app/code/Magento/Customer/Model/Address/DataProvider.php b/app/code/Magento/Customer/Model/Address/DataProvider.php new file mode 100644 index 0000000000000..e1dd68207cae5 --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/DataProvider.php @@ -0,0 +1,233 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Address; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Customer\Model\Address; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Ui\Component\Form\Element\Multiline; + +/** + * Dataprovider of customer addresses for customer address grid. + * @property \Magento\Customer\Model\ResourceModel\Address\Collection $collection + */ +class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var array + */ + private $loadedData; + + /** + * Allow to manage attributes, even they are hidden on storefront + * + * @var bool + */ + private $allowToShowHiddenAttributes; + + /* + * @var ContextInterface + */ + private $context; + + /** + * @var array + */ + private $bannedInputTypes = ['media_image']; + + /** + * @var array + */ + private static $attributesToEliminate = [ + 'region', + 'vat_is_valid', + 'vat_request_date', + 'vat_request_id', + 'vat_request_success' + ]; + + /** + * @var FileUploaderDataResolver + */ + private $fileUploaderDataResolver; + + /** + * @var AttributeMetadataResolver + */ + private $attributeMetadataResolver; + + /** + * DataProvider constructor. + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param CollectionFactory $addressCollectionFactory + * @param CustomerRepositoryInterface $customerRepository + * @param Config $eavConfig + * @param ContextInterface $context + * @param FileUploaderDataResolver $fileUploaderDataResolver + * @param AttributeMetadataResolver $attributeMetadataResolver + * @param array $meta + * @param array $data + * @param bool $allowToShowHiddenAttributes + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + CollectionFactory $addressCollectionFactory, + CustomerRepositoryInterface $customerRepository, + Config $eavConfig, + ContextInterface $context, + FileUploaderDataResolver $fileUploaderDataResolver, + AttributeMetadataResolver $attributeMetadataResolver, + array $meta = [], + array $data = [], + $allowToShowHiddenAttributes = true + ) { + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + $this->collection = $addressCollectionFactory->create(); + $this->collection->addAttributeToSelect('*'); + $this->customerRepository = $customerRepository; + $this->allowToShowHiddenAttributes = $allowToShowHiddenAttributes; + $this->context = $context; + $this->fileUploaderDataResolver = $fileUploaderDataResolver; + $this->attributeMetadataResolver = $attributeMetadataResolver; + $this->meta['general']['children'] = $this->getAttributesMeta( + $eavConfig->getEntityType('customer_address') + ); + } + + /** + * Get Addresses data and process customer default billing & shipping addresses + * + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getData(): array + { + if (null !== $this->loadedData) { + return $this->loadedData; + } + $items = $this->collection->getItems(); + /** @var Address $item */ + foreach ($items as $item) { + $addressId = $item->getEntityId(); + $item->load($addressId); + $this->loadedData[$addressId] = $item->getData(); + $customerId = $this->loadedData[$addressId]['parent_id']; + /** @var \Magento\Customer\Model\Customer $customer */ + $customer = $this->customerRepository->getById($customerId); + $defaultBilling = $customer->getDefaultBilling(); + $defaultShipping = $customer->getDefaultShipping(); + $this->prepareAddressData($addressId, $this->loadedData, $defaultBilling, $defaultShipping); + $this->fileUploaderDataResolver->overrideFileUploaderData($item, $this->loadedData[$addressId]); + } + + if (null === $this->loadedData) { + $this->loadedData[''] = $this->getDefaultData(); + } + + return $this->loadedData; + } + + /** + * Prepare address data + * + * @param int $addressId + * @param array $addresses + * @param string|null $defaultBilling + * @param string|null $defaultShipping + * @return void + */ + private function prepareAddressData($addressId, array &$addresses, $defaultBilling, $defaultShipping): void + { + if (null !== $defaultBilling && $addressId === $defaultBilling) { + $addresses[$addressId]['default_billing'] = '1'; + } + if (null !== $defaultShipping && $addressId === $defaultShipping) { + $addresses[$addressId]['default_shipping'] = '1'; + } + foreach ($this->meta['general']['children'] as $attributeName => $attributeMeta) { + if ($attributeMeta['arguments']['data']['config']['dataType'] === Multiline::NAME + && isset($this->loadedData[$addressId][$attributeName]) + && !\is_array($this->loadedData[$addressId][$attributeName]) + ) { + $this->loadedData[$addressId][$attributeName] = explode( + "\n", + $this->loadedData[$addressId][$attributeName] + ); + } + } + } + + /** + * Get default customer data for adding new address + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @return array + */ + private function getDefaultData(): array + { + $parentId = $this->context->getRequestParam('parent_id'); + $customer = $this->customerRepository->getById($parentId); + $data = [ + 'parent_id' => $parentId, + 'firstname' => $customer->getFirstname(), + 'lastname' => $customer->getLastname() + ]; + + return $data; + } + + /** + * Get attributes meta + * + * @param Type $entityType + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttributesMeta(Type $entityType): array + { + $meta = []; + $attributes = $entityType->getAttributeCollection(); + /* @var AbstractAttribute $attribute */ + foreach ($attributes as $attribute) { + if (\in_array($attribute->getFrontendInput(), $this->bannedInputTypes, true)) { + continue; + } + if (\in_array($attribute->getAttributeCode(), self::$attributesToEliminate, true)) { + continue; + } + + $meta[$attribute->getAttributeCode()] = $this->attributeMetadataResolver->getAttributesMeta( + $attribute, + $entityType, + $this->allowToShowHiddenAttributes, + $this->getRequestFieldName() + ); + } + $this->attributeMetadataResolver->processWebsiteMeta($meta); + + return $meta; + } +} diff --git a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php new file mode 100644 index 0000000000000..c22cc9a4f23f4 --- /dev/null +++ b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php @@ -0,0 +1,241 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model; + +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Ui\DataProvider\EavValidationRules; +use Magento\Ui\Component\Form\Field; +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\Config\Share as ShareConfig; + +/** + * Class to build meta data of the customer or customer address attribute + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AttributeMetadataResolver +{ + /** + * EAV attribute properties to fetch from meta storage + * @var array + */ + private static $metaProperties = [ + 'dataType' => 'frontend_input', + 'visible' => 'is_visible', + 'required' => 'is_required', + 'label' => 'frontend_label', + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + ]; + + /** + * Form element mapping + * + * @var array + */ + private static $formElement = [ + 'text' => 'input', + 'hidden' => 'input', + 'boolean' => 'checkbox', + ]; + + /** + * @var CountryWithWebsites + */ + private $countryWithWebsiteSource; + + /** + * @var EavValidationRules + */ + private $eavValidationRules; + + /** + * @var FileUploaderDataResolver + */ + private $fileUploaderDataResolver; + + /** + * @var ContextInterface + */ + private $context; + + /** + * @var ShareConfig + */ + private $shareConfig; + + /** + * @param CountryWithWebsites $countryWithWebsiteSource + * @param EavValidationRules $eavValidationRules + * @param \Magento\Customer\Model\FileUploaderDataResolver $fileUploaderDataResolver + * @param ContextInterface $context + * @param ShareConfig $shareConfig + */ + public function __construct( + CountryWithWebsites $countryWithWebsiteSource, + EavValidationRules $eavValidationRules, + fileUploaderDataResolver $fileUploaderDataResolver, + ContextInterface $context, + ShareConfig $shareConfig + ) { + $this->countryWithWebsiteSource = $countryWithWebsiteSource; + $this->eavValidationRules = $eavValidationRules; + $this->fileUploaderDataResolver = $fileUploaderDataResolver; + $this->context = $context; + $this->shareConfig = $shareConfig; + } + + /** + * Get meta data of the customer or customer address attribute + * + * @param AbstractAttribute $attribute + * @param Type $entityType + * @param bool $allowToShowHiddenAttributes + * @param string $requestFieldName + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getAttributesMeta( + AbstractAttribute $attribute, + Type $entityType, + bool $allowToShowHiddenAttributes, + string $requestFieldName + ): array { + $meta = $this->modifyBooleanAttributeMeta($attribute); + // use getDataUsingMethod, since some getters are defined and apply additional processing of returning value + foreach (self::$metaProperties as $metaName => $origName) { + $value = $attribute->getDataUsingMethod($origName); + $meta['arguments']['data']['config'][$metaName] = ($metaName === 'label') ? __($value) : $value; + if ('frontend_input' === $origName) { + $meta['arguments']['data']['config']['formElement'] = self::$formElement[$value] ?? $value; + } + } + + if ($attribute->usesSource()) { + if ($attribute->getAttributeCode() === AddressInterface::COUNTRY_ID) { + $meta['arguments']['data']['config']['options'] = $this->countryWithWebsiteSource + ->getAllOptions(); + } else { + $meta['arguments']['data']['config']['options'] = $attribute->getSource()->getAllOptions(); + } + } + + $rules = $this->eavValidationRules->build($attribute, $meta['arguments']['data']['config']); + if (!empty($rules)) { + $meta['arguments']['data']['config']['validation'] = $rules; + } + + $meta['arguments']['data']['config']['componentType'] = Field::NAME; + $meta['arguments']['data']['config']['visible'] = $this->canShowAttribute( + $attribute, + $requestFieldName, + $allowToShowHiddenAttributes + ); + + $this->fileUploaderDataResolver->overrideFileUploaderMetadata( + $entityType, + $attribute, + $meta['arguments']['data']['config'] + ); + + return $meta; + } + + /** + * Detect can we show attribute on specific form or not + * + * @param AbstractAttribute $customerAttribute + * @param string $requestFieldName + * @param bool $allowToShowHiddenAttributes + * @return bool + */ + private function canShowAttribute( + AbstractAttribute $customerAttribute, + string $requestFieldName, + bool $allowToShowHiddenAttributes + ) { + $userDefined = (bool)$customerAttribute->getIsUserDefined(); + if (!$userDefined) { + return $customerAttribute->getIsVisible(); + } + + $canShowOnForm = $this->canShowAttributeInForm($customerAttribute, $requestFieldName); + + return ($allowToShowHiddenAttributes && $canShowOnForm) || + (!$allowToShowHiddenAttributes && $canShowOnForm && $customerAttribute->getIsVisible()); + } + + /** + * Check whether the specific attribute can be shown in form: customer registration, customer edit, etc... + * + * @param AbstractAttribute $customerAttribute + * @param string $requestFieldName + * @return bool + */ + private function canShowAttributeInForm(AbstractAttribute $customerAttribute, string $requestFieldName): bool + { + $isRegistration = $this->context->getRequestParam($requestFieldName) === null; + + if ($customerAttribute->getEntityType()->getEntityTypeCode() === 'customer') { + return \is_array($customerAttribute->getUsedInForms()) && + ( + (\in_array('customer_account_create', $customerAttribute->getUsedInForms(), true) + && $isRegistration) || + (\in_array('customer_account_edit', $customerAttribute->getUsedInForms(), true) + && !$isRegistration) + ); + } + return \is_array($customerAttribute->getUsedInForms()) && + \in_array('customer_address_edit', $customerAttribute->getUsedInForms(), true); + } + + /** + * Modify boolean attribute meta data + * + * @param AttributeInterface $attribute + * @return array + */ + private function modifyBooleanAttributeMeta(AttributeInterface $attribute): array + { + $meta = []; + if ($attribute->getFrontendInput() === 'boolean') { + $meta['arguments']['data']['config']['prefer'] = 'toggle'; + $meta['arguments']['data']['config']['valueMap'] = [ + 'true' => '1', + 'false' => '0', + ]; + } + + return $meta; + } + + /** + * Add global scope parameter and filter options to website meta + * + * @param array $meta + * @return void + */ + public function processWebsiteMeta(&$meta): void + { + if (isset($meta[CustomerInterface::WEBSITE_ID]) && $this->shareConfig->isGlobalScope()) { + $meta[CustomerInterface::WEBSITE_ID]['arguments']['data']['config']['isGlobalScope'] = 1; + } + + if (isset($meta[AddressInterface::COUNTRY_ID]) && !$this->shareConfig->isGlobalScope()) { + $meta[AddressInterface::COUNTRY_ID]['arguments']['data']['config']['filterBy'] = [ + 'target' => 'customer_form.customer_form_data_source:data.customer.website_id', + 'field' => 'website_ids' + ]; + } + } +} diff --git a/app/code/Magento/Customer/Model/Customer/DataProvider.php b/app/code/Magento/Customer/Model/Customer/DataProvider.php index 31f126e937c92..5bad0a41aadc3 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProvider.php +++ b/app/code/Magento/Customer/Model/Customer/DataProvider.php @@ -5,14 +5,11 @@ */ namespace Magento\Customer\Model\Customer; -use Magento\Customer\Api\AddressMetadataInterface; -use Magento\Customer\Api\CustomerMetadataInterface; use Magento\Customer\Api\Data\AddressInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\Address; use Magento\Customer\Model\Attribute; use Magento\Customer\Model\Customer; -use Magento\Customer\Model\FileProcessor; use Magento\Customer\Model\FileProcessorFactory; use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; use Magento\Customer\Model\ResourceModel\Customer\Collection; @@ -28,12 +25,15 @@ use Magento\Ui\Component\Form\Element\Multiline; use Magento\Ui\Component\Form\Field; use Magento\Ui\DataProvider\EavValidationRules; +use Magento\Customer\Model\FileUploaderDataResolver; /** * Supplies the data for the customer UI component * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) * + * @deprecated \Magento\Customer\Model\Customer\DataProviderWithDefaultAddresses is used instead * @api * @since 100.0.2 */ @@ -111,21 +111,6 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider */ protected $session; - /** - * @var FileProcessorFactory - */ - private $fileProcessorFactory; - - /** - * File types allowed for file_uploader UI component - * - * @var array - */ - private $fileUploaderTypes = [ - 'image', - 'file', - ]; - /** * Customer fields that must be removed * @@ -149,6 +134,11 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider */ private $allowToShowHiddenAttributes; + /** + * @var FileUploaderDataResolver + */ + private $fileUploaderDataResolver; + /** * @param string $name * @param string $primaryFieldName @@ -162,8 +152,9 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider * @param array $data * @param ContextInterface $context * @param bool $allowToShowHiddenAttributes - * @throws \Magento\Framework\Exception\LocalizedException + * @param FileUploaderDataResolver|null $fileUploaderDataResolver * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( $name, @@ -177,7 +168,8 @@ public function __construct( array $meta = [], array $data = [], ContextInterface $context = null, - $allowToShowHiddenAttributes = true + $allowToShowHiddenAttributes = true, + $fileUploaderDataResolver = null ) { parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->eavValidationRules = $eavValidationRules; @@ -185,9 +177,10 @@ public function __construct( $this->collection->addAttributeToSelect('*'); $this->eavConfig = $eavConfig; $this->filterPool = $filterPool; - $this->fileProcessorFactory = $fileProcessorFactory ?: $this->getFileProcessorFactory(); $this->context = $context ?: ObjectManager::getInstance()->get(ContextInterface::class); $this->allowToShowHiddenAttributes = $allowToShowHiddenAttributes; + $this->fileUploaderDataResolver = $fileUploaderDataResolver + ?: ObjectManager::getInstance()->get(FileUploaderDataResolver::class); $this->meta['customer']['children'] = $this->getAttributesMeta( $this->eavConfig->getEntityType('customer') ); @@ -228,7 +221,7 @@ public function getData() foreach ($items as $customer) { $result['customer'] = $customer->getData(); - $this->overrideFileUploaderData($customer, $result['customer']); + $this->fileUploaderDataResolver->overrideFileUploaderData($customer, $result['customer']); $result['customer'] = array_diff_key( $result['customer'], @@ -243,7 +236,7 @@ public function getData() $result['address'][$addressId] = $address->getData(); $this->prepareAddressData($addressId, $result['address'], $result['customer']); - $this->overrideFileUploaderData($address, $result['address'][$addressId]); + $this->fileUploaderDataResolver->overrideFileUploaderData($address, $result['address'][$addressId]); } $this->loadedData[$customer->getId()] = $result; } @@ -258,75 +251,6 @@ public function getData() return $this->loadedData; } - /** - * Override file uploader UI component data - * - * Overrides data for attributes with frontend_input equal to 'image' or 'file'. - * - * @param Customer|Address $entity - * @param array $entityData - * @return void - */ - private function overrideFileUploaderData($entity, array &$entityData) - { - $attributes = $entity->getAttributes(); - foreach ($attributes as $attribute) { - /** @var Attribute $attribute */ - if (in_array($attribute->getFrontendInput(), $this->fileUploaderTypes)) { - $entityData[$attribute->getAttributeCode()] = $this->getFileUploaderData( - $entity->getEntityType(), - $attribute, - $entityData - ); - } - } - } - - /** - * Retrieve array of values required by file uploader UI component - * - * @param Type $entityType - * @param Attribute $attribute - * @param array $customerData - * @return array - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function getFileUploaderData( - Type $entityType, - Attribute $attribute, - array $customerData - ) { - $attributeCode = $attribute->getAttributeCode(); - - $file = isset($customerData[$attributeCode]) - ? $customerData[$attributeCode] - : ''; - - /** @var FileProcessor $fileProcessor */ - $fileProcessor = $this->getFileProcessorFactory()->create([ - 'entityTypeCode' => $entityType->getEntityTypeCode(), - ]); - - if (!empty($file) - && $fileProcessor->isExist($file) - ) { - $stat = $fileProcessor->getStat($file); - $viewUrl = $fileProcessor->getViewUrl($file, $attribute->getFrontendInput()); - - return [ - [ - 'file' => $file, - 'size' => isset($stat) ? $stat['size'] : 0, - 'url' => isset($viewUrl) ? $viewUrl : '', - 'name' => basename($file), - 'type' => $fileProcessor->getMimeType($file), - ], - ]; - } - - return []; - } - /** * Get attributes meta * @@ -372,7 +296,11 @@ protected function getAttributesMeta(Type $entityType) $meta[$code]['arguments']['data']['config']['componentType'] = Field::NAME; $meta[$code]['arguments']['data']['config']['visible'] = $this->canShowAttribute($attribute); - $this->overrideFileUploaderMetadata($entityType, $attribute, $meta[$code]['arguments']['data']['config']); + $this->fileUploaderDataResolver->overrideFileUploaderMetadata( + $entityType, + $attribute, + $meta[$code]['arguments']['data']['config'] + ); } $this->processWebsiteMeta($meta); @@ -470,97 +398,6 @@ private function processWebsiteMeta(&$meta) } } - /** - * Override file uploader UI component metadata - * - * Overrides metadata for attributes with frontend_input equal to 'image' or 'file'. - * - * @param Type $entityType - * @param AbstractAttribute $attribute - * @param array $config - * @return void - */ - private function overrideFileUploaderMetadata( - Type $entityType, - AbstractAttribute $attribute, - array &$config - ) { - if (in_array($attribute->getFrontendInput(), $this->fileUploaderTypes)) { - $maxFileSize = self::MAX_FILE_SIZE; - - if (isset($config['validation']['max_file_size'])) { - $maxFileSize = (int)$config['validation']['max_file_size']; - } - - $allowedExtensions = []; - - if (isset($config['validation']['file_extensions'])) { - $allowedExtensions = explode(',', $config['validation']['file_extensions']); - array_walk($allowedExtensions, function (&$value) { - $value = strtolower(trim($value)); - }); - } - - $allowedExtensions = implode(' ', $allowedExtensions); - - $entityTypeCode = $entityType->getEntityTypeCode(); - $url = $this->getFileUploadUrl($entityTypeCode); - - $config = [ - 'formElement' => 'fileUploader', - 'componentType' => 'fileUploader', - 'maxFileSize' => $maxFileSize, - 'allowedExtensions' => $allowedExtensions, - 'uploaderConfig' => [ - 'url' => $url, - ], - 'label' => $this->getMetadataValue($config, 'label'), - 'sortOrder' => $this->getMetadataValue($config, 'sortOrder'), - 'required' => $this->getMetadataValue($config, 'required'), - 'visible' => $this->getMetadataValue($config, 'visible'), - 'validation' => $this->getMetadataValue($config, 'validation'), - ]; - } - } - - /** - * Retrieve metadata value - * - * @param array $config - * @param string $name - * @param mixed $default - * @return mixed - */ - private function getMetadataValue($config, $name, $default = null) - { - $value = isset($config[$name]) ? $config[$name] : $default; - return $value; - } - - /** - * Retrieve URL to file upload - * - * @param string $entityTypeCode - * @return string - */ - private function getFileUploadUrl($entityTypeCode) - { - switch ($entityTypeCode) { - case CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER: - $url = 'customer/file/customer_upload'; - break; - - case AddressMetadataInterface::ENTITY_TYPE_ADDRESS: - $url = 'customer/file/address_upload'; - break; - - default: - $url = ''; - break; - } - return $url; - } - /** * Process attributes by frontend input type * @@ -610,19 +447,4 @@ protected function prepareAddressData($addressId, array &$addresses, array $cust } } } - - /** - * Get FileProcessorFactory instance - * - * @return FileProcessorFactory - * @deprecated 100.1.3 - */ - private function getFileProcessorFactory() - { - if ($this->fileProcessorFactory === null) { - $this->fileProcessorFactory = ObjectManager::getInstance() - ->get(\Magento\Customer\Model\FileProcessorFactory::class); - } - return $this->fileProcessorFactory; - } } diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php new file mode 100644 index 0000000000000..4d1bb2e6b9e99 --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php @@ -0,0 +1,200 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Customer; + +use Magento\Customer\Model\Address; +use Magento\Customer\Model\Customer; +use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Eav\Model\Entity\Type; +use Magento\Framework\Session\SessionManagerInterface; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\AttributeMetadataResolver; + +/** + * Refactored version of Magento\Customer\Model\Customer\DataProvider with eliminated usage of addresses collection. + */ +class DataProviderWithDefaultAddresses extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * @var array + */ + private $loadedData = []; + + /** + * @var SessionManagerInterface + */ + private $session; + + /** + * Customer fields that must be removed + * + * @var array + */ + private static $forbiddenCustomerFields = [ + 'password_hash', + 'rp_token', + 'confirmation', + ]; + + /** + * Allow to manage attributes, even they are hidden on storefront + * + * @var bool + */ + private $allowToShowHiddenAttributes; + + /** + * @var \Magento\Directory\Model\CountryFactory + */ + private $countryFactory; + + /** + * @var FileUploaderDataResolver + */ + private $fileUploaderDataResolver; + + /** + * @var AttributeMetadataResolver + */ + private $attributeMetadataResolver; + + /** + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param CustomerCollectionFactory $customerCollectionFactory + * @param Config $eavConfig + * @param \Magento\Directory\Model\CountryFactory $countryFactory + * @param SessionManagerInterface $session + * @param FileUploaderDataResolver $fileUploaderDataResolver + * @param AttributeMetadataResolver $attributeMetadataResolver + * @param bool $allowToShowHiddenAttributes + * @param array $meta + * @param array $data + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + string $name, + string $primaryFieldName, + string $requestFieldName, + CustomerCollectionFactory $customerCollectionFactory, + Config $eavConfig, + \Magento\Directory\Model\CountryFactory $countryFactory, + SessionManagerInterface $session, + FileUploaderDataResolver $fileUploaderDataResolver, + AttributeMetadataResolver $attributeMetadataResolver, + $allowToShowHiddenAttributes = true, + array $meta = [], + array $data = [] + ) { + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + $this->collection = $customerCollectionFactory->create(); + $this->collection->addAttributeToSelect('*'); + $this->allowToShowHiddenAttributes = $allowToShowHiddenAttributes; + $this->session = $session; + $this->countryFactory = $countryFactory; + $this->fileUploaderDataResolver = $fileUploaderDataResolver; + $this->attributeMetadataResolver = $attributeMetadataResolver; + $this->meta['customer']['children'] = $this->getAttributesMeta( + $eavConfig->getEntityType('customer') + ); + } + + /** + * Get data + * + * @return array + */ + public function getData(): array + { + if (!empty($this->loadedData)) { + return $this->loadedData; + } + $items = $this->collection->getItems(); + /** @var Customer $customer */ + foreach ($items as $customer) { + $result['customer'] = $customer->getData(); + + $this->fileUploaderDataResolver->overrideFileUploaderData($customer, $result['customer']); + + $result['customer'] = array_diff_key( + $result['customer'], + array_flip(self::$forbiddenCustomerFields) + ); + unset($result['address']); + + $result['default_billing_address'] = $this->prepareDefaultAddress( + $customer->getDefaultBillingAddress() + ); + $result['default_shipping_address'] = $this->prepareDefaultAddress( + $customer->getDefaultShippingAddress() + ); + $result['customer_id'] = $customer->getId(); + + $this->loadedData[$customer->getId()] = $result; + } + + $data = $this->session->getCustomerFormData(); + if (!empty($data)) { + $customerId = $data['customer']['entity_id'] ?? null; + $this->loadedData[$customerId] = $data; + $this->session->unsCustomerFormData(); + } + + return $this->loadedData; + } + + /** + * Prepare default address data. + * + * @param Address|false $address + * @return array + */ + private function prepareDefaultAddress($address): array + { + $addressData = []; + + if (!empty($address)) { + $addressData = $address->getData(); + if (isset($addressData['street']) && !\is_array($address['street'])) { + $addressData['street'] = explode("\n", $addressData['street']); + } + $addressData['country'] = $this->countryFactory->create() + ->loadByCode($addressData['country_id'])->getName(); + } + + return $addressData; + } + + /** + * Get attributes meta + * + * @param Type $entityType + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttributesMeta(Type $entityType): array + { + $meta = []; + $attributes = $entityType->getAttributeCollection(); + /* @var AbstractAttribute $attribute */ + foreach ($attributes as $attribute) { + $meta[$attribute->getAttributeCode()] = $this->attributeMetadataResolver->getAttributesMeta( + $attribute, + $entityType, + $this->allowToShowHiddenAttributes, + $this->getRequestFieldName() + ); + } + $this->attributeMetadataResolver->processWebsiteMeta($meta); + + return $meta; + } +} diff --git a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php new file mode 100644 index 0000000000000..535bfe97bc457 --- /dev/null +++ b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php @@ -0,0 +1,204 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model; + +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Api\CustomerMetadataInterface; + +/** + * Class to retrieve file uploader data for customer and customer address file & image attributes + */ +class FileUploaderDataResolver +{ + /** + * Maximum file size allowed for file_uploader UI component + * This constant was copied from deprecated data provider \Magento\Customer\Model\Customer\DataProvider + */ + private const MAX_FILE_SIZE = 2097152; + + /** + * @var FileProcessorFactory + */ + private $fileProcessorFactory; + + /** + * File types allowed for file_uploader UI component + * + * @var array + */ + private $fileUploaderTypes = [ + 'image', + 'file', + ]; + + /** + * @param FileProcessorFactory $fileProcessorFactory + */ + public function __construct( + FileProcessorFactory $fileProcessorFactory + ) { + $this->fileProcessorFactory = $fileProcessorFactory; + } + + /** + * Override file uploader UI component data + * + * Overrides data for attributes with frontend_input equal to 'image' or 'file'. + * + * @param Customer|Address $entity + * @param array $entityData + * @return void + */ + public function overrideFileUploaderData($entity, array &$entityData): void + { + $attributes = $entity->getAttributes(); + foreach ($attributes as $attribute) { + /** @var Attribute $attribute */ + if (\in_array($attribute->getFrontendInput(), $this->fileUploaderTypes, true)) { + $entityData[$attribute->getAttributeCode()] = $this->getFileUploaderData( + $entity->getEntityType(), + $attribute, + $entityData + ); + } + } + } + + /** + * Retrieve array of values required by file uploader UI component + * + * @param Type $entityType + * @param Attribute $attribute + * @param array $customerData + * @return array + */ + private function getFileUploaderData( + Type $entityType, + Attribute $attribute, + array $customerData + ): array { + $attributeCode = $attribute->getAttributeCode(); + + $file = $customerData[$attributeCode] ?? null; + + /** @var FileProcessor $fileProcessor */ + $fileProcessor = $this->fileProcessorFactory->create([ + 'entityTypeCode' => $entityType->getEntityTypeCode(), + ]); + + if (!empty($file) + && $fileProcessor->isExist($file) + ) { + $stat = $fileProcessor->getStat($file); + $viewUrl = $fileProcessor->getViewUrl($file, $attribute->getFrontendInput()); + + return [ + [ + 'file' => $file, + 'size' => null !== $stat ? $stat['size'] : 0, + 'url' => $viewUrl ?? '', + 'name' => basename($file), + 'type' => $fileProcessor->getMimeType($file), + ], + ]; + } + + return []; + } + + /** + * Override file uploader UI component metadata + * + * Overrides metadata for attributes with frontend_input equal to 'image' or 'file'. + * + * @param Type $entityType + * @param AbstractAttribute $attribute + * @param array $config + * @return void + */ + public function overrideFileUploaderMetadata( + Type $entityType, + AbstractAttribute $attribute, + array &$config + ): void { + if (\in_array($attribute->getFrontendInput(), $this->fileUploaderTypes, true)) { + $maxFileSize = self::MAX_FILE_SIZE; + + if (isset($config['validation']['max_file_size'])) { + $maxFileSize = (int)$config['validation']['max_file_size']; + } + + $allowedExtensions = []; + + if (isset($config['validation']['file_extensions'])) { + $allowedExtensions = explode(',', $config['validation']['file_extensions']); + array_walk($allowedExtensions, function (&$value) { + $value = strtolower(trim($value)); + }); + } + + $allowedExtensions = implode(' ', $allowedExtensions); + + $entityTypeCode = $entityType->getEntityTypeCode(); + $url = $this->getFileUploadUrl($entityTypeCode); + + $config = [ + 'formElement' => 'fileUploader', + 'componentType' => 'fileUploader', + 'maxFileSize' => $maxFileSize, + 'allowedExtensions' => $allowedExtensions, + 'uploaderConfig' => [ + 'url' => $url, + ], + 'label' => $this->getMetadataValue($config, 'label'), + 'sortOrder' => $this->getMetadataValue($config, 'sortOrder'), + 'required' => $this->getMetadataValue($config, 'required'), + 'visible' => $this->getMetadataValue($config, 'visible'), + 'validation' => $this->getMetadataValue($config, 'validation'), + ]; + } + } + + /** + * Retrieve metadata value + * + * @param array $config + * @param string $name + * @param mixed $default + * @return mixed + */ + private function getMetadataValue($config, $name, $default = null) + { + return $config[$name] ?? $default; + } + + /** + * Retrieve URL to file upload + * + * @param string $entityTypeCode + * @return string + */ + private function getFileUploadUrl($entityTypeCode): string + { + switch ($entityTypeCode) { + case CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER: + $url = 'customer/file/customer_upload'; + break; + + case AddressMetadataInterface::ENTITY_TYPE_ADDRESS: + $url = 'customer/file/address_upload'; + break; + + default: + $url = ''; + break; + } + return $url; + } +} diff --git a/app/code/Magento/Customer/Model/Metadata/Form/File.php b/app/code/Magento/Customer/Model/Metadata/Form/File.php index e6e9c2b50c068..227e85ed98f91 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/File.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/File.php @@ -15,6 +15,8 @@ use Magento\Framework\Filesystem; /** + * Processes files that are save for customer. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class File extends AbstractData @@ -66,7 +68,7 @@ class File extends AbstractData * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Customer\Api\Data\AttributeMetadataInterface $attribute * @param \Magento\Framework\Locale\ResolverInterface $localeResolver - * @param null $value + * @param string|array $value * @param string $entityTypeCode * @param bool $isAjax * @param \Magento\Framework\Url\EncoderInterface $urlEncoder @@ -101,7 +103,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function extractValue(\Magento\Framework\App\RequestInterface $request) @@ -109,7 +111,7 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) $extend = $this->_getRequestValue($request); $attrCode = $this->getAttribute()->getAttributeCode(); - if ($this->_requestScope) { + if ($this->_requestScope || !isset($_FILES[$attrCode])) { $value = []; if (strpos($this->_requestScope, '/') !== false) { $scopes = explode('/', $this->_requestScope); @@ -160,8 +162,7 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) } /** - * Validate file by attribute validate rules - * Return array of errors + * Validate file by attribute validate rules. Returns array of errors. * * @param array $value * @return string[] @@ -232,7 +233,7 @@ protected function _isUploadedFile($filename) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -273,7 +274,7 @@ public function validateValue($value) } /** - * {@inheritdoc} + * @inheritdoc * * @return ImageContentInterface|array|string|null */ @@ -358,7 +359,7 @@ protected function processInputFieldValue($value) } /** - * {@inheritdoc} + * @inheritdoc */ public function restoreValue($value) { @@ -366,7 +367,7 @@ public function restoreValue($value) } /** - * {@inheritdoc} + * @inheritdoc */ public function outputValue($format = \Magento\Customer\Model\Metadata\ElementFactory::OUTPUT_FORMAT_TEXT) { diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php new file mode 100644 index 0000000000000..4e0347059086f --- /dev/null +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Grid/Collection.php @@ -0,0 +1,143 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\ResourceModel\Address\Grid; + +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Api\Search\AggregationInterface; +use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; + +/** + * Class getting collection of addresses assigned to customer + */ +class Collection extends AbstractCollection implements SearchResultInterface +{ + /** + * @var AggregationInterface + */ + private $aggregations; + + /** + * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory + * @param \Psr\Log\LoggerInterface $logger + * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param string $mainTable + * @param string $eventPrefix + * @param string $eventObject + * @param string $resourceModel + * @param string $model + * @param \Magento\Framework\DB\Adapter\AdapterInterface|string|null $connection + * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory, + \Psr\Log\LoggerInterface $logger, + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, + \Magento\Framework\Event\ManagerInterface $eventManager, + $mainTable, + $eventPrefix, + $eventObject, + $resourceModel, + $model = \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class, + $connection = null, + \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null + ) { + $this->_eventPrefix = $eventPrefix; + $this->_eventObject = $eventObject; + $this->_init($model, $resourceModel); + $this->setMainTable($mainTable); + $this->_idFieldName = 'entity_id'; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $connection, + $resource + ); + } + + /** + * @inheritdoc + * + * @return AggregationInterface + */ + public function getAggregations() + { + return $this->aggregations; + } + + /** + * @inheritdoc + * + * @param AggregationInterface $aggregations + * @return $this + */ + public function setAggregations($aggregations) + { + $this->aggregations = $aggregations; + return $this; + } + + /** + * Get search criteria. + * + * @return \Magento\Framework\Api\SearchCriteriaInterface|null + */ + public function getSearchCriteria() + { + return null; + } + + /** + * Set search criteria. + * + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null) + { + return $this; + } + + /** + * Get total count. + * + * @return int + */ + public function getTotalCount() + { + return $this->getSize(); + } + + /** + * Set total count. + * + * @param int $totalCount + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setTotalCount($totalCount) + { + return $this; + } + + /** + * Set items list. + * + * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setItems(array $items = null) + { + return $this; + } +} diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php b/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php index d473a4dc01891..ae342a1b10dd8 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address/Relation.php @@ -7,6 +7,8 @@ */ namespace Magento\Customer\Model\ResourceModel\Address; +use Magento\Customer\Model\Address; +use Magento\Customer\Model\Customer; use Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationInterface; /** @@ -36,19 +38,14 @@ public function __construct(\Magento\Customer\Model\CustomerFactory $customerFac public function processRelation(\Magento\Framework\Model\AbstractModel $object) { /** - * @var $object \Magento\Customer\Model\Address + * @var $object Address */ - if (!$object->getIsCustomerSaveTransaction() && $this->isAddressDefault($object)) { + if (!$object->getIsCustomerSaveTransaction() && $object->getId()) { $customer = $this->customerFactory->create()->load($object->getCustomerId()); - $changedAddresses = []; - - if ($object->getIsDefaultBilling()) { - $changedAddresses['default_billing'] = $object->getId(); - } - if ($object->getIsDefaultShipping()) { - $changedAddresses['default_shipping'] = $object->getId(); - } + $changedAddresses = []; + $changedAddresses = $this->getDefaultBillingChangedAddress($object, $customer, $changedAddresses); + $changedAddresses = $this->getDefaultShippingChangedAddress($object, $customer, $changedAddresses); if ($changedAddresses) { $customer->getResource()->getConnection()->update( @@ -60,9 +57,62 @@ public function processRelation(\Magento\Framework\Model\AbstractModel $object) } } + /** + * Get default billing changed address + * + * @param Address $object + * @param Customer $customer + * @param array $changedAddresses + * @return array + */ + private function getDefaultBillingChangedAddress( + Address $object, + Customer $customer, + array $changedAddresses + ): array { + if ($object->getIsDefaultBilling()) { + $changedAddresses['default_billing'] = $object->getId(); + } elseif ($customer->getDefaultBillingAddress() + && $object->getIsDefaultBilling() === false + && (int)$customer->getDefaultBillingAddress()->getId() === (int)$object->getId() + ) { + $changedAddresses['default_billing'] = null; + } + + return $changedAddresses; + } + + /** + * Get default shipping changed address + * + * @param Address $object + * @param Customer $customer + * @param array $changedAddresses + * @return array + */ + private function getDefaultShippingChangedAddress( + Address $object, + Customer $customer, + array $changedAddresses + ): array { + if ($object->getIsDefaultShipping()) { + $changedAddresses['default_shipping'] = $object->getId(); + } elseif ($customer->getDefaultShippingAddress() + && $object->getIsDefaultShipping() === false + && (int)$customer->getDefaultShippingAddress()->getId() === (int)$object->getId() + ) { + $changedAddresses['default_shipping'] = null; + } + + return $changedAddresses; + } + /** * Checks if address has chosen as default and has had an id * + * @deprecated Is not used anymore due to changes in logic of save of address. + * If address was default and becomes not default than default address id for customer must be + * set to null * @param \Magento\Framework\Model\AbstractModel $object * @return bool */ diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index 77049dac5aa0c..b25838e245488 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -204,6 +204,10 @@ public function save(CustomerInterface $customer, $passwordHash = null) if ($storeId === null) { $customerModel->setStoreId($this->storeManager->getStore()->getId()); } + // Need to use attribute set or future updates can cause data loss + if (!$customerModel->getAttributeSetId()) { + $customerModel->setAttributeSetId(CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER); + } $this->populateCustomerWithSecureData($customerModel, $passwordHash); // If customer email was changed, reset RpToken info if ($prevCustomerData && $prevCustomerData->getEmail() !== $customerModel->getEmail()) { @@ -342,7 +346,7 @@ public function getList(SearchCriteriaInterface $searchCriteria) ->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left') ->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left') ->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left') - ->joinAttribute('company', 'customer_address/company', 'default_billing', null, 'left'); + ->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left'); $this->collectionProcessor->process($searchCriteria, $collection); diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml index 0071acf2ef1e0..4c458c66ca65b 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml @@ -22,6 +22,9 @@ <fillField stepKey="FillEmail" selector="{{AdminCustomerAccountInformationSection.email}}" userInput="{{customerData.email}}"/> <selectOption stepKey="selectStoreView" selector="{{AdminCustomerAccountInformationSection.storeView}}" userInput="{{storeView}}"/> <scrollToTopOfPage stepKey="scrollToTopOfThePage"/> + <click stepKey="saveCustomer" selector="{{AdminCustomerAccountInformationSection.saveCustomerAndContinueEdit}}"/> + <waitForPageLoad stepKey="waitForCustomersPage"/> + <see stepKey="seeSuccessMessage" userInput="You saved the customer."/> <click stepKey="goToAddresses" selector="{{AdminCustomerAccountInformationSection.addressesButton}}"/> <waitForPageLoad stepKey="waitForAddresses"/> <click stepKey="clickOnAddNewAddress" selector="{{AdminCustomerAddressesSection.addNewAddress}}"/> @@ -36,8 +39,7 @@ <selectOption stepKey="selectState" selector="{{AdminCustomerAddressesSection.state}}" userInput="{{address.state}}"/> <fillField stepKey="fillZip" selector="{{AdminCustomerAddressesSection.zip}}" userInput="{{address.postcode}}"/> <fillField stepKey="fillPhoneNumber" selector="{{AdminCustomerAddressesSection.phoneNumber}}" userInput="{{address.telephone}}"/> - <click stepKey="save" selector="{{AdminCustomerAccountInformationSection.saveCustomer}}"/> - <waitForPageLoad stepKey="waitForCustomersPage"/> - <see stepKey="seeSuccessMessage" userInput="You saved the customer."/> + <click stepKey="saveAddress" selector="{{AdminCustomerAddressesSection.saveAddress}}"/> + <waitForPageLoad stepKey="waitForAddressSave"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml index 5b3a7a70aa2e6..af918e8208566 100755 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml @@ -20,12 +20,28 @@ <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEdit"/> <waitForPageLoad stepKey="waitForPageLoad2" /> </actionGroup> + <actionGroup name="OpenEditCustomerAddressFromAdminActionGroup"> + <arguments> + <argument name="address"/> + </arguments> + <click selector="{{AdminCustomerAccountInformationSection.addressesButton}}" stepKey="openAddressesTab"/> + <waitForElementVisible selector="{{AdminCustomerAddressFiltersSection.filtersButton}}" stepKey="waitForComponentLoad"/> + <click selector="{{AdminCustomerAddressFiltersSection.filtersButton}}" stepKey="openAddressesFilter"/> + <fillField userInput="{{address.firstname}}" selector="{{AdminCustomerAddressFiltersSection.firstnameInput}}" stepKey="fillFirstname"/> + <fillField userInput="{{address.lastname}}" selector="{{AdminCustomerAddressFiltersSection.lastnameInput}}" stepKey="fillLastname"/> + <fillField userInput="{{address.telephone}}" selector="{{AdminCustomerAddressFiltersSection.telephoneInput}}" stepKey="fillCountry"/> + <fillField userInput="{{address.postcode}}" selector="{{AdminCustomerAddressFiltersSection.postcodeInput}}" stepKey="fillPostcode"/> + <click selector="{{AdminCustomerAddressFiltersSection.applyFilter}}" stepKey="applyAddressesFilter"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <click selector="{{AdminCustomerAddressGridSection.firstRowSelectActionLink}}" stepKey="clickAction"/> + <click selector="{{AdminCustomerAddressGridSection.firstRowEditActionLink}}" stepKey="clickEdit"/> + <waitForPageLoad stepKey="waitForModalWindow" /> + </actionGroup> <actionGroup name="DeleteCustomerFromAdminActionGroup"> <arguments> - <argument name="customer"/> + <argument name="customer" defaultValue="CustomerEntityOne"/> </arguments> <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> - <waitForPageLoad stepKey="waitForPageLoad1" /> <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> <fillField selector="{{AdminDataGridHeaderSection.search}}" userInput="{{customer.email}}" stepKey="fillSearch"/> <click selector="{{AdminDataGridHeaderSection.submitSearch}}" stepKey="clickSubmit"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index 227085fce9de4..76acf6e865963 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -9,10 +9,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SignUpNewUserFromStorefrontActionGroup"> <arguments> - <argument name="Customer"/> + <argument name="Customer" defaultValue="CustomerEntityOne"/> </arguments> - <amOnPage stepKey="amOnStorefrontPage" url="/"/> - <waitForPageLoad stepKey="waitForStorefrontPage"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> <click stepKey="clickOnCreateAccountLink" selector="{{StorefrontPanelHeaderSection.createAnAccountLink}}"/> <fillField stepKey="fillFirstName" userInput="{{Customer.firstname}}" selector="{{StorefrontCustomerCreateFormSection.firstnameField}}"/> <fillField stepKey="fillLastName" userInput="{{Customer.lastname}}" selector="{{StorefrontCustomerCreateFormSection.lastnameField}}"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml new file mode 100644 index 0000000000000..a45fcf31f7b3f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddNewCustomerAddressActionGroup"> + <amOnPage url="customer/address/new/" stepKey="OpenCustomerAddNewAddress"/> + <arguments> + <argument name="Address"/> + </arguments> + <fillField stepKey="fillFirstName" userInput="{{Address.firstname}}" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="{{Address.lastname}}" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <fillField stepKey="fillCompanyName" userInput="{{Address.company}}" selector="{{StorefrontCustomerAddressFormSection.company}}"/> + <fillField stepKey="fillPhoneNumber" userInput="{{Address.telephone}}" selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{Address.street[0]}}" selector="{{StorefrontCustomerAddressFormSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{Address.city}}" selector="{{StorefrontCustomerAddressFormSection.city}}"/> + <selectOption stepKey="selectState" userInput="{{Address.state}}" selector="{{StorefrontCustomerAddressFormSection.state}}"/> + <fillField stepKey="fillZip" userInput="{{Address.postcode}}" selector="{{StorefrontCustomerAddressFormSection.zip}}"/> + <selectOption stepKey="selectCountry" userInput="{{Address.country}}" selector="{{StorefrontCustomerAddressFormSection.country}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + </actionGroup> + <actionGroup name="StorefrontAddCustomerDefaultAddressActionGroup"> + <amOnPage url="customer/address/new/" stepKey="OpenCustomerAddNewAddress"/> + <arguments> + <argument name="Address"/> + </arguments> + <fillField stepKey="fillFirstName" userInput="{{Address.firstname}}" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="{{Address.lastname}}" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <fillField stepKey="fillCompanyName" userInput="{{Address.company}}" selector="{{StorefrontCustomerAddressFormSection.company}}"/> + <fillField stepKey="fillPhoneNumber" userInput="{{Address.telephone}}" selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{Address.street[0]}}" selector="{{StorefrontCustomerAddressFormSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{Address.city}}" selector="{{StorefrontCustomerAddressFormSection.city}}"/> + <selectOption stepKey="selectState" userInput="{{Address.state}}" selector="{{StorefrontCustomerAddressFormSection.state}}"/> + <fillField stepKey="fillZip" userInput="{{Address.postcode}}" selector="{{StorefrontCustomerAddressFormSection.zip}}"/> + <selectOption stepKey="selectCountry" userInput="{{Address.country}}" selector="{{StorefrontCustomerAddressFormSection.country}}"/> + <click stepKey="checkUseAsDefaultBillingAddressCheckBox" selector="{{StorefrontCustomerAddressFormSection.useAsDefaultBillingAddressCheckBox}}"/> + <click stepKey="checkUseAsDefaultShippingAddressCheckBox" selector="{{StorefrontCustomerAddressFormSection.useAsDefaultShippingAddressCheckBox}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml new file mode 100644 index 0000000000000..fc5c1b881752e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CustomerLogoutStorefrontByMenuItemsActionGroup"> + <conditionalClick selector="{{StorefrontPanelHeaderSection.customerWelcome}}" + dependentSelector="{{StorefrontPanelHeaderSection.customerWelcomeMenu}}" + visible="false" + stepKey="clickHeaderCustomerMenuButton" /> + <click selector="{{StorefrontPanelHeaderSection.customerLogoutLink}}" stepKey="clickSignOutButton" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml index 5497b083ab5ad..de97bb47de796 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml @@ -11,4 +11,11 @@ <actionGroup name="StorefrontCustomerLogoutActionGroup"> <amOnPage url="{{StorefrontCustomerLogoutPage.url}}" stepKey="storefrontSignOut"/> </actionGroup> + + <actionGroup name="StorefrontSignOutActionGroup"> + <click selector="{{StoreFrontSignOutSection.customerAccount}}" stepKey="clickCustomerButton"/> + <click selector="{{StoreFrontSignOutSection.signOut}}" stepKey="clickToSignOut"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You are signed out" stepKey="signOut"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index eafb85d662c81..85b94b67c0eda 100755 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -44,12 +44,29 @@ <data key="city">Austin</data> <data key="state">Texas</data> <data key="country_id">US</data> + <data key="country">United States</data> <data key="postcode">78729</data> <data key="telephone">512-345-6789</data> <data key="default_billing">Yes</data> <data key="default_shipping">Yes</data> <requiredEntity type="region">RegionTX</requiredEntity> </entity> + <entity name="US_Address_TX_Default_Billing" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>7700 West Parmer Lane</item> + </array> + <data key="city">Austin</data> + <data key="state">Texas</data> + <data key="country_id">US</data> + <data key="country">United States</data> + <data key="postcode">78729</data> + <data key="telephone">512-345-6789</data> + <data key="default_billing">Yes</data> + <requiredEntity type="region">RegionTX</requiredEntity> + </entity> <entity name="US_Address_NY" type="address"> <data key="firstname">John</data> <data key="lastname">Doe</data> @@ -68,6 +85,39 @@ <requiredEntity type="region">RegionNY</requiredEntity> <data key="country">United States</data> </entity> + <entity name="US_Address_NY_Default_Shipping" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">368</data> + <array key="street"> + <item>368 Broadway St.</item> + <item>113</item> + </array> + <data key="city">New York</data> + <data key="state">New York</data> + <data key="country_id">US</data> + <data key="postcode">10001</data> + <data key="telephone">512-345-6789</data> + <data key="default_shipping">Yes</data> + <requiredEntity type="region">RegionNY</requiredEntity> + <data key="country">United States</data> + </entity> + <entity name="US_Address_NY_Not_Default_Address" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">368</data> + <array key="street"> + <item>368 Broadway St.</item> + <item>Apt. 113</item> + </array> + <data key="city">New York</data> + <data key="state">New York</data> + <data key="country_id">US</data> + <data key="postcode">10001</data> + <data key="telephone">512-345-6789</data> + <requiredEntity type="region">RegionNY</requiredEntity> + <data key="country">United States</data> + </entity> <entity name="US_Address_CA" type="address"> <data key="firstname">John</data> <data key="lastname">Doe</data> @@ -108,6 +158,23 @@ <data key="country_id">GB</data> <data key="telephone">444-44-444-44</data> </entity> + <entity name="US_Address_Utah" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>1234 Some Utah address</item> + </array> + <data key="city">Provo</data> + <data key="state">Utah</data> + <data key="country_id">US</data> + <data key="country">United States</data> + <data key="postcode">84001</data> + <data key="telephone">512-345-6789</data> + <data key="default_billing">Yes</data> + <data key="default_shipping">Yes</data> + <requiredEntity type="region">RegionUT</requiredEntity> + </entity> <entity name="UK_Simple_Address" extends="UK_Not_Default_Address"> <array key="street"> <item>172, Westminster Bridge Rd</item> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml new file mode 100644 index 0000000000000..3cbd70d342824 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CustomerAccountSharingDefault" type="customer_account_sharing_config"> + <requiredEntity type="account_share_scope_value">CustomerAccountSharingPerWebsite</requiredEntity> + </entity> + <entity name="CustomerAccountSharingPerWebsite" type="account_share_scope_value"> + <data key="value">1</data> + </entity> + + <entity name="CustomerAccountSharingGlobal" type="customer_account_sharing_config"> + <requiredEntity type="account_share_scope_value">GlobalCustomerAccountSharing</requiredEntity> + </entity> + <entity name="GlobalCustomerAccountSharing" type="account_share_scope_value"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index 6f5ade53e6790..ab42dfe85e4ff 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -33,7 +33,7 @@ <!--requiredEntity type="extension_attribute">ExtensionAttributeSimple</requiredEntity--> </entity> <entity name="Simple_US_Customer" type="customer"> - <data key="group_id">0</data> + <data key="group_id">1</data> <data key="default_billing">true</data> <data key="default_shipping">true</data> <data key="email" unique="prefix">John.Doe@example.com</data> @@ -45,6 +45,16 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="Simple_Customer_Without_Address" type="customer"> + <data key="group_id">1</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + </entity> <entity name="Simple_US_Customer_Multiple_Addresses" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> @@ -59,6 +69,34 @@ <requiredEntity type="address">US_Address_NY</requiredEntity> <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> </entity> + <entity name="Simple_US_Customer_Multiple_Addresses_No_Default_Address" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_NY_Not_Default_Address</requiredEntity> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + </entity> + <entity name="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_TX_Default_Billing</requiredEntity> + <requiredEntity type="address">US_Address_NY_Default_Shipping</requiredEntity> + </entity> <entity name="Simple_US_Customer_NY" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> @@ -102,4 +140,30 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_CA</requiredEntity> </entity> + <entity name="Simple_US_Utah_Customer" type="customer"> + <data key="group_id">1</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_Utah</requiredEntity> + </entity> + <entity name="Simple_GB_Customer" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">Jane.Doe@example.com</data> + <data key="firstname">Jane</data> + <data key="lastname">Doe</data> + <data key="fullname">Jane Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml index b1da921f7ca59..280bae7de411a 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml @@ -27,4 +27,9 @@ <data key="region_code">NY</data> <data key="region_id">43</data> </entity> + <entity name="RegionUT" type="region"> + <data key="region">Utah</data> + <data key="region_code">UT</data> + <data key="region_id">58</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml new file mode 100644 index 0000000000000..41701bfac11ad --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CustomerAccountShareConfig" dataType="customer_account_sharing_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/customer/" + successRegex="/messages-message-success/" returnRegex="" method="POST"> + <object key="groups" dataType="customer_account_sharing_config"> + <object key="account_share" dataType="customer_account_sharing_config"> + <object key="fields" dataType="customer_account_sharing_config"> + <object key="scope" dataType="account_share_scope_value"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml index 31dad24ba8372..d662c5cef6032 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml @@ -9,6 +9,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminEditCustomerPage" url="/customer/index/edit/id/{{var1}}" area="admin" module="Magento_Customer" parameterized="true"> <section name="AdminCustomerAccountInformationSection"/> + <section name="AdminCustomerAddressesGridSection"/> + <section name="AdminCustomerAddressesGridActionsSection"/> + <section name="AdminCustomerAddressesSection"/> <section name="AdminCustomerMainActionsSection"/> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml new file mode 100644 index 0000000000000..9c1fc7aa8a88d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerLogoutSuccessPage" url="customer/account/logoutSuccess/" area="storefront" module="Magento_Customer"/> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index 76f692db1db3e..d5a410164a6f1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -20,6 +20,7 @@ <element name="groupValue" type="button" selector="//span[text()='{{groupValue}}']" parameterized="true"/> <element name="associateToWebsite" type="select" selector="//select[@name='customer[website_id]']"/> <element name="saveCustomer" type="button" selector="//button[@title='Save Customer']"/> + <element name="saveCustomerAndContinueEdit" type="button" selector="//button[@title='Save and Continue Edit']"/> <element name="storeView" type="select" selector="//select[@name='customer[sendemail_store_id]']"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml new file mode 100644 index 0000000000000..b9a3839ff9894 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressFiltersSection"> + <element name="filtersButton" type="button" selector="button[data-action='grid-filter-expand']" timeout="30"/> + <element name="firstnameInput" type="input" selector="input[name=firstname]"/> + <element name="lastnameInput" type="input" selector="input[name=lastname]"/> + <element name="streetInput" type="input" selector="input[name=street]"/> + <element name="cityInput" type="input" selector="input[name=city]"/> + <element name="stateSelector" type="select" selector="input[name=input]"/> + <element name="postcodeInput" type="input" selector="input[name=postcode]"/> + <element name="countryInput" type="input" selector="select[name=country_id]"/> + <element name="telephoneInput" type="input" selector="input[name=telephone]"/> + <element name="applyFilter" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> + <element name="clearAll" type="button" selector=".admin__data-grid-header .action-tertiary.action-clear" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridMainActionsSection.xml new file mode 100644 index 0000000000000..f226d49e3bf54 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridMainActionsSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerGridMainActionsSection"> + <element name="addNewAddress" type="button" selector=".add-new-address-button" timeout="30"/> + <element name="actions" type="text" selector=".admin__data-grid-header-row .action-select"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml new file mode 100644 index 0000000000000..fb153a7c102a5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressGridSection"> + <element name="customerGrid" type="text" selector="table[data-role='grid']"/> + <element name="firstRowSelectActionLink" type="text" selector="tr[data-repeat-index='0'] .action-select" timeout="30"/> + <element name="firstRowEditActionLink" type="text" selector="tr[data-repeat-index='0'] [data-action='item-edit']" timeout="30"/> + + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultBillingSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultBillingSection.xml new file mode 100644 index 0000000000000..a85c12fda1064 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultBillingSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressesDefaultBillingSection"> + <element name="addressDetails" type="text" selector="//div[@class='customer-default-billing-address-content']//div[@class='address_details']"/> + <element name="address" type="text" selector="//div[@class='customer-default-billing-address-content']//address//span"/> + <element name="editButton" type="text" selector="//button[@data-index='edit_billing_address']"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultShippingSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultShippingSection.xml new file mode 100644 index 0000000000000..610bb16874b8a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesDefaultShippingSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressesDefaultShippingSection"> + <element name="addressDetails" type="text" selector="//div[@class='customer-default-shipping-address-content']//div[@class='address_details']"/> + <element name="address" type="text" selector="//div[@class='customer-default-shipping-address-content']//address//span"/> + <element name="editButton" type="text" selector="//button[@data-index='edit_shipping_address']"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml new file mode 100644 index 0000000000000..d8d93814333ca --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressesGridActionsSection"> + <element name="spinner" type="button" selector=".spinner"/> + <element name="gridLoadingMask" type="button" selector=".admin__data-grid-loading-mask"/> + <element name="search" type="input" selector="#fulltext"/> + <element name="delete" type="button" selector="//*[contains(@class, 'admin__data-grid-header')]//span[contains(@class,'action-menu-item') and text()='Delete']"/> + <element name="actions" type="text" selector="//div[@class='admin__data-grid-header']//button[@class='action-select']"/> + <element name="filters" type="button" selector="button[data-action='grid-filter-expand']" timeout="30"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml new file mode 100644 index 0000000000000..85c086d01848b --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAddressesGridSection"> + <element name="customerAddressGrid" type="text" selector="table[data-role='grid']"/> + <element name="firstRowSelectLink" type="text" selector="//tr[contains(@data-repeat-index, '0')]//button[@class='action-select']"/> + <element name="firstRowEditLink" type="text" selector="//tr[contains(@data-repeat-index, '0')]//a[contains(@data-action,'item-edit')]" timeout="30"/> + <element name="firstRowSetAsDefaultBillingLink" type="text" selector="//tr[contains(@data-repeat-index, '0')]//a[contains(@data-action,'item-setDefaultBilling')]" timeout="30"/> + <element name="firstRowSetAsDefaultShippingLink" type="text" selector="//tr[contains(@data-repeat-index, '0')]//a[contains(@data-action,'item-setDefaultShipping')]" timeout="30"/> + <element name="firstRowDeleteLink" type="text" selector="//tr[contains(@data-repeat-index, '0')]//a[contains(@data-action,'item-delete')]" timeout="30"/> + <element name="firstRowCheckbox" type="checkbox" selector="//tr[contains(@data-repeat-index, '0')]//input[contains(@data-action, 'select-row')]"/> + <element name="secondRowCheckbox" type="checkbox" selector="//tr[contains(@data-repeat-index, '1')]//input[contains(@data-action, 'select-row')]"/> + <element name="checkboxByName" type="checkbox" selector="//div[contains(text(),'{{customer}}')]/ancestor::tr[contains(@class, 'data-row')]//input[@class='admin__control-checkbox']" parameterized="true" /> + <element name="rowsInGrid" type="text" selector="//tr[contains(@class,'data-row')]"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml index 175c7ab573d37..8068f94032730 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml @@ -9,16 +9,26 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerAddressesSection"> - <element name="addNewAddress" type="button" selector="//span[text()='Add New Addresses']"/> - <element name="defaultBillingAddress" type="button" selector="//label[text()='Default Billing Address']"/> - <element name="defaultShippingAddress" type="button" selector="//label[text()='Default Shipping Address']"/> - <element name="firstNameForAddress" type="button" selector="//input[contains(@name, 'address')][contains(@name, 'firstname')]"/> - <element name="lastNameForAddress" type="button" selector="//input[contains(@name, 'address')][contains(@name, 'lastname')]"/> - <element name="streetAddress" type="button" selector="//input[contains(@name, 'street')]"/> - <element name="city" type="input" selector="//input[contains(@name, 'city')]"/> - <element name="country" type="select" selector="//select[contains(@name, 'country_id')]"/> - <element name="state" type="select" selector="//select[contains(@name, 'address[new_0][region_id]')]"/> - <element name="zip" type="input" selector="//input[contains(@name, 'postcode')]"/> - <element name="phoneNumber" type="input" selector="//input[contains(@name, 'telephone')]"/> + <element name="addNewAddress" type="button" selector="//span[text()='Add New Address']"/> + <element name="defaultBillingAddress" type="button" selector="div[data-index=default_billing] .admin__actions-switch-label"/> + <element name="defaultBillingAddressCheckBox" type="input" selector="//div[@class='admin__field-control']//input[@name='default_billing']"/> + <element name="defaultShippingAddress" type="button" selector="div[data-index=default_shipping] .admin__actions-switch-label"/> + <element name="defaultShippingAddressCheckBox" type="input" selector="//div[@class='admin__field-control']//input[@name='default_shipping']"/> + <element name="firstNameForAddress" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'firstname')]"/> + <element name="lastNameForAddress" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'lastname')]"/> + <element name="streetAddress" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'street')]"/> + <element name="city" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'city')]"/> + <element name="company" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'company')]"/> + <element name="region" type="select" selector="//div[@class='admin__field-control']//select[@name='region_id']"/> + <element name="regionId" type="select" selector="//div[@class='admin__field-control']//select[@name='region_id']//option[@data-title='{{regionName}}']" parameterized="true"/> + <element name="country" type="select" selector="//div[@class='admin__field-control']//select[contains(@name, 'country_id')]"/> + <element name="countryId" type="input" selector="//div[@class='admin__field-control']//select[contains(@name, 'country_id')]//option[@value='{{countryName}}']" parameterized="true"/> + <element name="state" type="select" selector="//div[@class='admin__field-control']//select[contains(@name, 'region_id')]"/> + <element name="zip" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'postcode')]"/> + <element name="phoneNumber" type="input" selector="//div[@class='admin__field-control']//input[contains(@name, 'telephone')]"/> + <element name="saveAddress" type="button" selector="//button[@title='Save']"/> + <element name="customerAddressRow" type="input" selector="//tbody//tr//td//div[contains(., '{{var1}}')]" parameterized="true"/> + <element name="deleteButton" type="button" selector="//button[@id='delete']"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml index c44e3f249216b..02d9bc2eb5f12 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml @@ -15,6 +15,6 @@ <element name="emailInput" type="input" selector="input[name=email]"/> <element name="apply" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> <element name="clearAllFilters" type="text" selector=".admin__current-filters-actions-wrap.action-clear"/> - <element name="clearAll" type="button" selector=".admin__data-grid-header .action-tertiary.action-clear"/> + <element name="clearAll" type="button" selector=".admin__data-grid-header .action-tertiary.action-clear" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml index a28c6d5ff5e2d..f5bbb84eaa593 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditCustomerInformationSection"> <element name="orders" type="button" selector="#tab_orders_content" timeout="30"/> + <element name="addresses" type="button" selector="//a[@id='tab_address']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontSignOutSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StoreFrontSignOutSection.xml similarity index 100% rename from app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontSignOutSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/StoreFrontSignOutSection.xml diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml new file mode 100644 index 0000000000000..112ced1bc375f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerAddressFormSection"> + <element name="firstName" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'firstname')]"/> + <element name="lastName" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'lastname')]"/> + <element name="company" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'company')]"/> + <element name="phoneNumber" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'telephone')]"/> + <element name="streetAddress" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'street')]"/> + <element name="city" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'city')]"/> + <element name="state" type="select" selector="//form[@class='form-address-edit']//select[contains(@name, 'region_id')]"/> + <element name="zip" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'postcode')]"/> + <element name="country" type="select" selector="//form[@class='form-address-edit']//select[contains(@name, 'country_id')]"/> + <element name="useAsDefaultBillingAddressCheckBox" type="input" selector="//form[@class='form-address-edit']//input[@name='default_billing']"/> + <element name="useAsDefaultShippingAddressCheckBox" type="input" selector="//form[@class='form-address-edit']//input[@name='default_shipping']"/> + <element name="saveAddress" type="button" selector="//button[@title='Save Address']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml index 05bbc559defac..29a2f549274a7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml @@ -9,7 +9,13 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerAddressesSection"> - <element name="addressesList" type="text" selector=".block-addresses-list" /> - <element name="deleteAdditionalAddress" type="button" selector="//ol[@class='items addresses']/li[@class='item'][{{var}}]//a[@class='action delete']" parameterized="true"/> + <element name="defaultBillingAddress" type="text" selector=".box-address-billing" /> + <element name="editDefaultBillingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Billing Address']" timeout="30"/> + <element name="defaultShippingAddress" type="text" selector=".box-address-shipping" /> + <element name="editDefaultShippingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Shipping Address']" timeout="30"/> + <element name="addressesList" type="text" selector=".additional-addresses" /> + <element name="deleteAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action delete']" parameterized="true"/> + <element name="editAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action edit']" parameterized="true" timeout="30"/> + <element name="addNewAddress" type="button" selector="//span[text()='Add New Address']"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index 8480dc6e9d2a6..25c07ca9cb3c9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -13,4 +13,10 @@ <element name="passwordField" type="input" selector="#pass"/> <element name="signInAccountButton" type="button" selector="#send2" timeout="30"/> </section> + <section name="StorefrontCustomerSignInPopupFormSection"> + <element name="errorMessage" type="input" selector="[data-ui-id='checkout-cart-validationmessages-message-error']"/> + <element name="email" type="input" selector="#customer-email"/> + <element name="password" type="input" selector="#pass"/> + <element name="signIn" type="button" selector="#send2" timeout="30"/> + </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index a0c83f5bc491b..1955c6a417ba9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -12,5 +12,8 @@ <element name="WelcomeMessage" type="text" selector=".greet.welcome span"/> <element name="createAnAccountLink" type="select" selector=".panel.header li:nth-child(3)" timeout="30"/> <element name="notYouLink" type="button" selector=".greet.welcome span a"/> + <element name="customerWelcome" type="text" selector=".panel.header .customer-welcome"/> + <element name="customerWelcomeMenu" type="text" selector=".panel.header .customer-welcome .customer-menu"/> + <element name="customerLogoutLink" type="text" selector=".panel.header .customer-welcome .customer-menu .authorization-link a" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml new file mode 100644 index 0000000000000..fba7ebd2d4d5e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAddNewDefaultBillingShippingCustomerAddressTest"> + <annotations> + <stories value="Add new default billing/shipping customer address"/> + <title value="Add new default billing/shipping customer address"/> + <description value="Add new default billing/shipping customer address on customer addresses tab"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94814"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses_No_Default_Address" stepKey="customer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customers. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. Open *Addresses* tab on edit customer page and press *Add New Address* button + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <waitForPageLoad stepKey="waitForAddresses"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <seeElement selector="{{AdminCustomerAddressesDefaultBillingSection.address}}" stepKey="seeDefaultBillingAddressSectionBeforeChangingDefaultAddress"/> + <see userInput="The customer does not have default billing address" selector="{{AdminCustomerAddressesDefaultBillingSection.address}}" stepKey="assertThereIsNoDefaultBillingAddressSet"/> + <seeElement selector="{{AdminCustomerAddressesDefaultShippingSection.address}}" stepKey="seeDefaultShippingAddressSectionBeforeChangingDefaultAddress"/> + <see userInput="The customer does not have default shipping address" selector="{{AdminCustomerAddressesDefaultShippingSection.address}}" stepKey="assertThereIsNoDefaultShippingAddressSet"/> + <click selector="{{AdminCustomerAddressesSection.addNewAddress}}" stepKey="clickAddNewAddressButton"/> + <waitForPageLoad stepKey="waitForAddUpdateCustomerAddressForm"/> + <!--Step4. Fill all the fields with test data and press *Save* button--> + <click selector="{{AdminCustomerAddressesSection.defaultBillingAddress}}" stepKey="enableDefaultBillingAddress"/> + <click selector="{{AdminCustomerAddressesSection.defaultShippingAddress}}" stepKey="enableDefaultShippingAddress"/> + <fillField userInput="{{US_Address_TX.firstname}}" selector="{{AdminCustomerAddressesSection.firstNameForAddress}}" stepKey="fillFirstName"/> + <fillField userInput="{{US_Address_TX.lastname}}" selector="{{AdminCustomerAddressesSection.lastNameForAddress}}" stepKey="fillLastName"/> + <fillField userInput="{{US_Address_TX.company}}" selector="{{AdminCustomerAddressesSection.company}}" stepKey="fillCompany"/> + <fillField userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesSection.streetAddress}}" stepKey="fillStreet"/> + <fillField userInput="{{US_Address_TX.city}}" selector="{{AdminCustomerAddressesSection.city}}" stepKey="fillCity"/> + <click selector="{{AdminCustomerAddressesSection.country}}" stepKey="clickCountryToOpenListOfCountries"/> + <click selector="{{AdminCustomerAddressesSection.countryId(US_Address_TX.country_id)}}" stepKey="fillCountry"/> + <fillField userInput="{{US_Address_TX.postcode}}" selector="{{AdminCustomerAddressesSection.zip}}" stepKey="fillPostcode"/> + <fillField userInput="{{US_Address_TX.telephone}}" selector="{{AdminCustomerAddressesSection.phoneNumber}}" stepKey="fillTelephone"/> + <click selector="{{AdminCustomerAddressesSection.region}}" stepKey="clickRegionToOpenListOfRegions"/> + <click selector="{{AdminCustomerAddressesSection.regionId(US_Address_TX.state)}}" stepKey="fillRegion"/> + <click selector="{{AdminCustomerAddressesSection.saveAddress}}" stepKey="clickSaveCustomerAddressOnAddUpdateAddressForm"/> + <waitForPageLoad stepKey="waitForNewAddressIsCreated"/> + <see userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="assertDefaultBillingAddressIsChanged"/> + <see userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="assertDefaultShippingAddressIsChanged"/> + <click selector="{{AdminCustomerAddressesDefaultBillingSection.editButton}}" stepKey="clickEditDefaultBillingAddress"/> + <waitForPageLoad stepKey="waitForCustomerAddressAddUpdateFormLoad"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultBillingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultBillingIsEnabledCustomerAddressAddUpdateForm"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultShippingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultShippingIsEnabledOnCustomerAddressAddUpdateForm"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index 6bde63d900c17..78bae7ad60dd8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -19,6 +19,9 @@ <group value="customer"/> <group value="create"/> </annotations> + <before> + <magentoCLI command="indexer:reindex customer_grid" stepKey="reindexCustomerGrid"/> + </before> <after> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml new file mode 100644 index 0000000000000..4f501c27352bf --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteCustomerAddressesFromTheGridTest"> + <annotations> + <title value="Admin delete customer addresses from the grid"/> + <description value="Admin delete customer addresses from the grid"/> + <features value="Module/ Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94850"/> + <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> + <group value="customer"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customerts. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <!--Step4. Click *Select* link in *Actions* column for target additional address--> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSelectLink}}" stepKey="clickOnSelectLinkInFirstRow"/> + <!--Step5. Click *Delete*--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowDeleteLink}}" stepKey="chooseDeleteOptionInFirstRow"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad1"/> + <!--Step6. Press *Ok* button on the pop-up--> + <click selector="{{AdminCustomerAddressesGridActionsSection.ok}}" stepKey="clickOkOnPopup"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad2"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeOnlyOneCustomerAddressesInGrid"/> + <!--Step7. Delete last customer address--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSelectLink}}" stepKey="clickOnSelectLinkInFirstRow2"/> + <click selector="{{AdminCustomerAddressesGridSection.firstRowDeleteLink}}" stepKey="chooseDeleteOptionInFirstRow2"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad3"/> + <click selector="{{AdminCustomerAddressesGridActionsSection.ok}}" stepKey="clickOkOnPopup2"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad4"/> + <see userInput="We couldn't find any records." selector="{{AdminCustomerAddressesGridSection.customerAddressGrid}}" stepKey="checkThatCustomerAddressesGridHasNoRecords"/> +</test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml new file mode 100644 index 0000000000000..a703c5a7c5d92 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteCustomerAddressesFromTheGridViaMassActionsTest"> + <annotations> + <title value="Admin delete customer addresses from the grid via mass actions"/> + <description value="Admin delete customer addresses from the grid via mass actions"/> + <features value="Module/ Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94951"/> + <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> + <group value="customer"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customerts. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <!-- - + Step4. Check checkboxes for several addresses open *Actions* dropdown at the top of addresses grid and select action *Delete* + Step5. Press *Ok* button on the pop-up + <!- --> + <click selector="{{AdminCustomerAddressesGridSection.firstRowCheckbox}}" stepKey="tickFirstRowCustomerAddressCheckbox"/> + <click selector="{{AdminCustomerAddressesGridSection.secondRowCheckbox}}" stepKey="tickSecondRowCustomerAddressCheckbox"/> + <click selector="{{AdminCustomerAddressesGridActionsSection.actions}}" stepKey="openActionsDropdown"/> + <click selector="{{AdminCustomerAddressesGridActionsSection.delete}}" stepKey="chooseDeleteOption"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad1"/> + <click selector="{{AdminCustomerAddressesGridActionsSection.ok}}" stepKey="clickOkOnPopup"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad2"/> + <see userInput="We couldn't find any records." selector="{{AdminCustomerAddressesGridSection.customerAddressGrid}}" stepKey="checkThatCustomerAddressesGridHasNoRecords"/> +</test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml new file mode 100644 index 0000000000000..bb455677d5e94 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteDefaultBillingCustomerAddressTest"> + <annotations> + <title value="Admin delete default billing customer address"/> + <description value="Admin delete default billing customer address"/> + <features value="Module/ Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94816"/> + <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> + <group value="customer"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customers. + Step2. On *Customers* page choose customer from preconditions and open it to edit + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> + </actionGroup> + <!--Step3. Open *Addresses* tab on edit customer page and click *edit* link near *Default Billing Address* block--> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <seeNumberOfElements userInput="2" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeTwoCustomerAddressesInGrid"/> + <click selector="{{AdminCustomerAddressesDefaultBillingSection.editButton}}" stepKey="clickEditNearDefaultBillingAddress"/> + <waitForPageLoad stepKey="waitForDefaultBillingAddressPopupLoad"/> + <!--Step4. Press *Delete* button--> + <click selector="{{AdminCustomerAddressesSection.deleteButton}}" stepKey="clickDeleteButton"/> + <waitForPageLoad stepKey="waitForConfirmationPopupLoad"/> + <click selector="{{AdminCustomerAddressesSection.ok}}" stepKey="clickOkOnPopup"/> + <waitForPageLoad stepKey="waitForDefaultBillingAddressPopupLoad2"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeOnlyOneCustomerAddressesInGrid"/> + <dontSee userInput="{{US_Address_NY.street[0]}}" stepKey="assertDefaultBillingIsSet"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml new file mode 100644 index 0000000000000..df317c8bf7012 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminEditDefaultBillingShippingCustomerAddressTest"> + <annotations> + <stories value="Edit default billing/shipping customer address"/> + <title value="Edit default billing/shipping customer address"/> + <description value="Edit default billing/shipping customer address on customer addresses tab"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94815"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="customer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customers. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. Open *Addresses* tab on edit customer page and press *Add New Address* button + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <waitForPageLoad stepKey="waitForAddresses"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <seeElement selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="seeDefaultBillingAddressSectionBeforeChangingDefaultAddress"/> + <see userInput="{{US_Address_NY.street[0]}}" selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="assertDefaultBillingAddressIsSetBeforeChangingDefaultAddress"/> + <seeElement selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="seeDefaultShippingAddressSectionBeforeChangingDefaultAddress"/> + <see userInput="{{US_Address_NY.street[0]}}" selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="assertDefaultShippingAddressIsSetBeforeChangingDefaultAddress"/> + <click selector="{{AdminCustomerAddressesSection.addNewAddress}}" stepKey="clickAddNewAddressButton"/> + <waitForPageLoad stepKey="waitForAddUpdateCustomerAddressForm"/> + <!--Step4. Fill all the fields with test data and press *Save* button--> + <click selector="{{AdminCustomerAddressesSection.defaultBillingAddress}}" stepKey="enableDefaultBillingAddress"/> + <click selector="{{AdminCustomerAddressesSection.defaultShippingAddress}}" stepKey="enableDefaultShippingAddress"/> + <fillField userInput="{{US_Address_TX.firstname}}" selector="{{AdminCustomerAddressesSection.firstNameForAddress}}" stepKey="fillFirstName"/> + <fillField userInput="{{US_Address_TX.lastname}}" selector="{{AdminCustomerAddressesSection.lastNameForAddress}}" stepKey="fillLastName"/> + <fillField userInput="{{US_Address_TX.company}}" selector="{{AdminCustomerAddressesSection.company}}" stepKey="fillCompany"/> + <fillField userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesSection.streetAddress}}" stepKey="fillStreet"/> + <fillField userInput="{{US_Address_TX.city}}" selector="{{AdminCustomerAddressesSection.city}}" stepKey="fillCity"/> + <click selector="{{AdminCustomerAddressesSection.country}}" stepKey="clickCountryToOpenListOfCountries"/> + <click selector="{{AdminCustomerAddressesSection.countryId(US_Address_TX.country_id)}}" stepKey="fillCountry"/> + <fillField userInput="{{US_Address_TX.postcode}}" selector="{{AdminCustomerAddressesSection.zip}}" stepKey="fillPostcode"/> + <fillField userInput="{{US_Address_TX.telephone}}" selector="{{AdminCustomerAddressesSection.phoneNumber}}" stepKey="fillTelephone"/> + <click selector="{{AdminCustomerAddressesSection.region}}" stepKey="clickRegionToOpenListOfRegions"/> + <click selector="{{AdminCustomerAddressesSection.regionId(US_Address_TX.state)}}" stepKey="fillRegion"/> + <click selector="{{AdminCustomerAddressesSection.saveAddress}}" stepKey="clickSaveCustomerAddressOnAddUpdateAddressForm"/> + <waitForPageLoad stepKey="waitForNewAddressIsCreated"/> + <see userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="assertDefaultBillingAddressIsChanged"/> + <see userInput="{{US_Address_TX.street[0]}}" selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="assertDefaultShippingAddressIsChanged"/> + <click selector="{{AdminCustomerAddressesDefaultBillingSection.editButton}}" stepKey="clickEditDefaultBillingAddress"/> + <waitForPageLoad stepKey="waitForCustomerAddressAddUpdateFormLoad"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultBillingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultBillingIsEnabledCustomerAddressAddUpdateForm"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultShippingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultShippingIsEnabledOnCustomerAddressAddUpdateForm"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml new file mode 100644 index 0000000000000..9f1c5e8cd923a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSearchCustomerAddressByKeywordTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSearchCustomerAddressByKeywordTest"> + <annotations> + <title value="Admin search customer address by keyword"/> + <description value="Admin search customer address by keyword"/> + <features value="Module/ Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94954"/> + <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> + <group value="customer"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customerts. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <!--Step4. Fill *Search by keyword* filed with the query and press enter or clock on the magnifier icon--> + <fillField userInput="{{US_Address_NY.street[0]}}" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="FillCustomerAddressStreetInSearchByKeyword"/> + <pressKey parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="pressEnterKey"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeOnlyOneCustomerAddressesInGrid"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml new file mode 100644 index 0000000000000..db8d4e1ee1eea --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultBillingAddressTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSetCustomerDefaultBillingAddressTest"> + <annotations> + <stories value="Set customer default billing address"/> + <title value="Admin should be able to set customer default billing address"/> + <description value="Admin should be able to set customer default billing address from customer addresses grid row actions"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94952"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses_No_Default_Address" stepKey="customer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customers. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <waitForPageLoad stepKey="waitForAddresses"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <fillField userInput="{{US_Address_NY_Not_Default_Address.street[0]}}" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="fillCustomerAddressStreetInSearchByKeyword"/> + <pressKey parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="pressEnterKey"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad"/> + <see userInput="The customer does not have default billing address" selector="{{AdminCustomerAddressesDefaultBillingSection.address}}" stepKey="assertThatThereIsNoDefaultBillingAddress"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeOnlyOneCustomerAddressesInGrid"/> + <!--Step4. Click *Select* link in *Actions* column for target additional address--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSelectLink}}" stepKey="clickSelectElementFromRow" /> + <!--Step4. Click *Set as default billing*--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSetAsDefaultBillingLink}}" stepKey="clickOnSetAddressAsDefaultBilling"/> + <!--Step5. Press *Ok* button on the pop-up--> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmSetAddressAsDefaultBilling"/> + <seeElement selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="seeDefaultBillingAddressSection"/> + <see userInput="{{US_Address_NY_Not_Default_Address.street[0]}}" selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" stepKey="assertDefaultBillingAddressIsSet"/> + <click selector="{{AdminCustomerAddressesDefaultBillingSection.editButton}}" stepKey="clickEditDefaultBillingAddress"/> + <waitForPageLoad stepKey="waitForCustomerAddressAddUpdateFormLoad"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultBillingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultBillingCheckboxIsCheckedOnCustomerAddressAddUpdateForm"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml new file mode 100644 index 0000000000000..6e83218176904 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminSetCustomerDefaultShippingAddressTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSetCustomerDefaultShippingAddressTest"> + <annotations> + <stories value="Set customer default shipping address"/> + <title value="Admin should be able to set customer default shipping address"/> + <description value="Admin should be able to set customer default shipping address from customer addresses grid row actions"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94953"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses_No_Default_Address" stepKey="customer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- - + Step1. Login to admin and go to Customers > All Customers. + Step2. On *Customers* page choose customer from preconditions and open it to edit + Step3. On edit customer page open *Addresses* tab and find a grid with the additional addresses + <!- --> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPage"> + <argument name="customer" value="Simple_US_Customer_Multiple_Addresses_No_Default_Address"/> + </actionGroup> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTab"/> + <waitForPageLoad stepKey="waitForAddresses"/> + <conditionalClick selector="{{AdminCustomerAddressFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerAddressFiltersSection.clearAll}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + <fillField userInput="{{US_Address_NY_Not_Default_Address.street[0]}}" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="fillCustomerAddressStreetInSearchByKeyword"/> + <pressKey parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" selector="{{AdminCustomerAddressesGridActionsSection.search}}" stepKey="pressEnterKey"/> + <waitForPageLoad stepKey="waitForCustomerAddressesGridPageLoad"/> + <see userInput="The customer does not have default shipping address" selector="{{AdminCustomerAddressesDefaultShippingSection.address}}" stepKey="assertThatThereIsNoDefaultShippingAddress"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesGridSection.rowsInGrid}}" stepKey="seeOnlyOneCustomerAddressesInGrid"/> + <!--Step4. Click *Select* link in *Actions* column for target additional address--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSelectLink}}" stepKey="clickSelectElementFromRow" /> + <!--Step4. Click *Set as default shipping*--> + <click selector="{{AdminCustomerAddressesGridSection.firstRowSetAsDefaultShippingLink}}" stepKey="clickOnSetAddressAsDefaultShipping"/> + <!--Step5. Press *Ok* button on the pop-up--> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmSetAddressAsDefaultShipping"/> + <seeElement selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="seeDefaultShippingAddressSection"/> + <see userInput="{{US_Address_NY_Not_Default_Address.street[0]}}" selector="{{AdminCustomerAddressesDefaultShippingSection.addressDetails}}" stepKey="assertDefaultShippingAddressIsSet"/> + <click selector="{{AdminCustomerAddressesDefaultShippingSection.editButton}}" stepKey="clickEditDefaultShippingAddress"/> + <waitForPageLoad stepKey="waitForCustomerAddressAddUpdateFormLoad"/> + <assertElementContainsAttribute selector="{{AdminCustomerAddressesSection.defaultShippingAddressCheckBox}}" attribute="value" expectedValue="1" stepKey="assertDefaultShippingCheckboxIsCheckedOnCustomerAddressAddUpdateForm"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml new file mode 100644 index 0000000000000..413bbfd06a539 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddNewCustomerAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new address"/> + <description value="Storefront user should be able to create a new address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97364"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_Customer_Without_Address" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="AmOnLogoutPage"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddNewCustomerAddressActionGroup" stepKey="AddNewAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesStreetOnDefaultBilling"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesCityOnDefaultBilling"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultBilling"/> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> + <test name="StorefrontAddCustomerDefaultAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new default billing/shipping address"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97364"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddCustomerDefaultAddressActionGroup" stepKey="AddNewDefaultAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesStreetOnDefaultBilling"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesCityOnDefaultBilling"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultBilling"/> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> + <test name="StorefrontAddCustomerNonDefaultAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new non default billing/shipping address"/> + <description value="Storefront user should be able to create a new non default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97500"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="AmOnLogoutPage"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddNewCustomerAddressActionGroup" stepKey="AddNewNonDefaultAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml new file mode 100644 index 0000000000000..d1b5fb2aa7d11 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUpdateCustomerDefaultBillingAddressFromBlockTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Add default customer address via the Storefront6"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97501"/> + <group value="customer"/> + <group value="update"/> + <skip> + <issueId value="MAGETWO-97504"/> + </skip> + </annotations> + <before> + <createData entity="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click stepKey="ClickEditDefaultBillingAddress" selector="{{StorefrontCustomerAddressesSection.editDefaultBillingAddress}}"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstNameBilling" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastNameBilling" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstNameBilling" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultBilling"/> + <see userInput="EditedLastNameBilling" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultBilling"/> + <see userInput="{{US_Address_NY_Default_Shipping.firstname}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultShipping"/> + <see userInput="{{US_Address_NY_Default_Shipping.lastname}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultShipping"/> + </test> + <test name="StorefrontUpdateCustomerDefaultShippingAddressFromBlockTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Add default customer address via the Storefront611"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97501"/> + <group value="customer"/> + <group value="update"/> + <skip> + <issueId value="MAGETWO-97504"/> + </skip> + </annotations> + <before> + <createData entity="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click stepKey="ClickEditDefaultShippingAddress" selector="{{StorefrontCustomerAddressesSection.editDefaultShippingAddress}}"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstNameShipping" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastNameShipping" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstNameShipping" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultShipping"/> + <see userInput="EditedLastNameShipping" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultShipping"/> + <see userInput="{{US_Address_TX_Default_Billing.firstname}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultBilling"/> + <see userInput="{{US_Address_TX_Default_Billing.lastname}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultBilling"/> + </test> + <test name="StorefrontUpdateCustomerAddressFromGridTest"> + <annotations> + <features value="Customer address"/> + <stories value="Add default customer address via the Storefront7"/> + <title value="Add default customer address via the Storefront7"/> + <description value="Storefront user should be able to create a new default address via the storefront2"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-97502"/> + <group value="customer"/> + <group value="update"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click selector="{{StorefrontCustomerAddressesSection.editAdditionalAddress('1')}}" stepKey="editAdditionalAddress"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstName" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastName" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstName" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressFirstNameOnGrid"/> + <see userInput="EditedLastName" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressLastNameOnGrid"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php new file mode 100644 index 0000000000000..31bcc37612302 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Block\Address; + +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory; + +/** + * Unit tests for \Magento\Customer\Block\Address\Grid class + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressCollectionFactory; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $currentCustomer; + + /** + * @var \Magento\Directory\Model\CountryFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $countryFactory; + + /** + * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlBuilder; + + /** + * @var \Magento\Customer\Block\Address\Grid + */ + private $gridBlock; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->currentCustomer = $this->getMockBuilder(\Magento\Customer\Helper\Session\CurrentCustomer::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomer']) + ->getMock(); + + $this->addressCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->countryFactory = $this->getMockBuilder(\Magento\Directory\Model\CountryFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->urlBuilder = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class); + + $this->gridBlock = $this->objectManager->getObject( + \Magento\Customer\Block\Address\Grid::class, + [ + 'addressCollectionFactory' => $this->addressCollectionFactory, + 'currentCustomer' => $this->currentCustomer, + 'countryFactory' => $this->countryFactory, + '_urlBuilder' => $this->urlBuilder + ] + ); + } + + /** + * Test for \Magento\Customer\Block\Address\Book::getChildHtml method with 'pager' argument + */ + public function testGetChildHtml() + { + $customerId = 1; + + /** @var \Magento\Framework\View\Element\BlockInterface|\PHPUnit_Framework_MockObject_MockObject $block */ + $block = $this->getMockBuilder(\Magento\Framework\View\Element\BlockInterface::class) + ->setMethods(['setCollection']) + ->getMockForAbstractClass(); + /** @var $layout \Magento\Framework\View\LayoutInterface|\PHPUnit_Framework_MockObject_MockObject */ + $layout = $this->getMockForAbstractClass(\Magento\Framework\View\LayoutInterface::class); + /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var \PHPUnit_Framework_MockObject_MockObject */ + $addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['setOrder', 'setCustomerFilter', 'load']) + ->getMock(); + + $layout->expects($this->atLeastOnce())->method('getChildName')->with('NameInLayout', 'pager') + ->willReturn('ChildName'); + $layout->expects($this->atLeastOnce())->method('renderElement')->with('ChildName', true) + ->willReturn('OutputString'); + $layout->expects($this->atLeastOnce())->method('createBlock') + ->with(\Magento\Theme\Block\Html\Pager::class, 'customer.addresses.pager')->willReturn($block); + $customer->expects($this->atLeastOnce())->method('getId')->willReturn($customerId); + $this->currentCustomer->expects($this->atLeastOnce())->method('getCustomer')->willReturn($customer); + $addressCollection->expects($this->atLeastOnce())->method('setOrder')->with('entity_id', 'desc') + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId]) + ->willReturnSelf(); + $this->addressCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($addressCollection); + $block->expects($this->atLeastOnce())->method('setCollection')->with($addressCollection)->willReturnSelf(); + $this->gridBlock->setNameInLayout('NameInLayout'); + $this->gridBlock->setLayout($layout); + $this->assertEquals('OutputString', $this->gridBlock->getChildHtml('pager')); + } + + /** + * Test for \Magento\Customer\Block\Address\Grid::getAddressEditUrl method + */ + public function testGetAddAddressUrl() + { + $addressId = 1; + $expectedUrl = 'expected_url'; + $this->urlBuilder->expects($this->atLeastOnce())->method('getUrl') + ->with('customer/address/edit', ['_secure' => true, 'id' => $addressId]) + ->willReturn($expectedUrl); + $this->assertEquals($expectedUrl, $this->gridBlock->getAddressEditUrl($addressId)); + } + + public function testGetAdditionalAddresses() + { + $customerId = 1; + /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var \PHPUnit_Framework_MockObject_MockObject */ + $addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator']) + ->getMock(); + $addressDataModel = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class); + $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'getDataModel']) + ->getMock(); + $collection = [$address, $address, $address]; + $address->expects($this->exactly(3))->method('getId') + ->willReturnOnConsecutiveCalls(1, 2, 3); + $address->expects($this->atLeastOnce())->method('getDataModel')->willReturn($addressDataModel); + $customer->expects($this->atLeastOnce())->method('getId')->willReturn($customerId); + $customer->expects($this->atLeastOnce())->method('getDefaultBilling')->willReturn('1'); + $customer->expects($this->atLeastOnce())->method('getDefaultShipping')->willReturn('2'); + + $this->currentCustomer->expects($this->atLeastOnce())->method('getCustomer')->willReturn($customer); + $addressCollection->expects($this->atLeastOnce())->method('setOrder')->with('entity_id', 'desc') + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId]) + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('getIterator') + ->willReturn(new \ArrayIterator($collection)); + $this->addressCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($addressCollection); + + $this->assertEquals($addressDataModel, $this->gridBlock->getAdditionalAddresses()[0]); + } + + /** + * Test for \Magento\Customer\ViewModel\CustomerAddress::getStreetAddress method + */ + public function testGetStreetAddress() + { + $street = ['Line 1', 'Line 2']; + $expectedAddress = 'Line 1, Line 2'; + $address = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->atLeastOnce())->method('getStreet')->willReturn($street); + $this->assertEquals($expectedAddress, $this->gridBlock->getStreetAddress($address)); + } + + /** + * Test for \Magento\Customer\ViewModel\CustomerAddress::getCountryByCode method + */ + public function testGetCountryByCode() + { + $countryId = 'US'; + $countryName = 'United States'; + $country = $this->getMockBuilder(\Magento\Directory\Model\Country::class) + ->disableOriginalConstructor() + ->setMethods(['loadByCode', 'getName']) + ->getMock(); + $this->countryFactory->expects($this->atLeastOnce())->method('create')->willReturn($country); + $country->expects($this->atLeastOnce())->method('loadByCode')->with($countryId)->willReturnSelf(); + $country->expects($this->atLeastOnce())->method('getName')->willReturn($countryName); + $this->assertEquals($countryName, $this->gridBlock->getCountryByCode($countryId)); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Address/DeleteButtonTest.php b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Address/DeleteButtonTest.php new file mode 100644 index 0000000000000..7b0da3bd422a6 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Address/DeleteButtonTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Block\Adminhtml\Edit\Address; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class for \Magento\Customer\Block\Adminhtml\Edit\Address\DeleteButton unit tests + */ +class DeleteButtonTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Customer\Model\AddressFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressFactory; + + /** + * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlBuilder; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressResourceModel; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $request; + + /** + * @var \Magento\Customer\Model\ResourceModel\AddressRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressRepository; + + /** + * @var \Magento\Customer\Block\Adminhtml\Edit\Address\DeleteButton + */ + private $deleteButton; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->addressFactory = $this->getMockBuilder(\Magento\Customer\Model\AddressFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlBuilder = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class); + $this->addressResourceModel = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address::class) + ->disableOriginalConstructor() + ->getMock(); + $this->request = $this->getMockForAbstractClass(\Magento\Framework\App\RequestInterface::class); + $this->addressRepository = $this->getMockBuilder( + \Magento\Customer\Model\ResourceModel\AddressRepository::class + ) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerHelper = new ObjectManagerHelper($this); + + $this->deleteButton = $objectManagerHelper->getObject( + \Magento\Customer\Block\Adminhtml\Edit\Address\DeleteButton::class, + [ + 'addressFactory' => $this->addressFactory, + 'urlBuilder' => $this->urlBuilder, + 'addressResourceModel' => $this->addressResourceModel, + 'request' => $this->request, + 'addressRepository' => $this->addressRepository, + ] + ); + } + + /** + * Unit test for \Magento\Customer\Block\Adminhtml\Edit\Address\DeleteButton::getButtonData() method + */ + public function testGetButtonData() + { + $addressId = 1; + $customerId = 2; + + /** @var \Magento\Customer\Model\Address|\PHPUnit_Framework_MockObject_MockObject $address */ + $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + ->disableOriginalConstructor() + ->getMock(); + $address->expects($this->atLeastOnce())->method('getEntityId')->willReturn($addressId); + $address->expects($this->atLeastOnce())->method('getCustomerId')->willReturn($customerId); + $this->addressFactory->expects($this->atLeastOnce())->method('create')->willReturn($address); + $this->request->expects($this->atLeastOnce())->method('getParam')->with('entity_id') + ->willReturn($addressId); + $this->addressResourceModel->expects($this->atLeastOnce())->method('load')->with($address, $addressId); + $this->addressRepository->expects($this->atLeastOnce())->method('getById')->with($addressId) + ->willReturn($address); + $this->urlBuilder->expects($this->atLeastOnce())->method('getUrl') + ->with( + \Magento\Customer\Ui\Component\Listing\Address\Column\Actions::CUSTOMER_ADDRESS_PATH_DELETE, + ['parent_id' => $customerId, 'id' => $addressId] + )->willReturn('url'); + + $buttonData = $this->deleteButton->getButtonData(); + $this->assertEquals('Delete', (string)$buttonData['label']); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php index f1629d61fe924..d234ebfb334d6 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php @@ -7,6 +7,7 @@ use Magento\Customer\Block\Form\Register; use Magento\Customer\Model\AccountManagement; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; /** * Test class for \Magento\Customer\Block\Form\Register. @@ -274,12 +275,13 @@ public function testGetRegionNull() } /** - * @param $isNewsletterEnabled - * @param $expectedValue + * @param boolean $isNewsletterEnabled + * @param string $isNewsletterActive + * @param boolean $expectedValue * * @dataProvider isNewsletterEnabledProvider */ - public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) + public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActive, $expectedValue) { $this->_moduleManager->expects( $this->once() @@ -290,6 +292,17 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) )->will( $this->returnValue($isNewsletterEnabled) ); + + $this->_scopeConfig->expects( + $this->any() + )->method( + 'getValue' + )->with( + PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE + )->will( + $this->returnValue($isNewsletterActive) + ); + $this->assertEquals($expectedValue, $this->_block->isNewsletterEnabled()); } @@ -298,7 +311,7 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) */ public function isNewsletterEnabledProvider() { - return [[true, true], [false, false]]; + return [[true, true, true], [true, false, false], [false, true, false], [false, false, false]]; } /** diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php new file mode 100644 index 0000000000000..1bf881ff0a933 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php @@ -0,0 +1,213 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Controller\Address; + +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SaveTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Customer\Controller\Adminhtml\Address\Save + */ + private $model; + + /** + * @var \Magento\Customer\Api\AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressRepositoryMock; + + /** + * @var \Magento\Customer\Model\Metadata\FormFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $formFactoryMock; + + /** + * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerRepositoryMock; + + /** + * @var \Magento\Framework\Api\DataObjectHelper|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataObjectHelperMock; + + /** + * @var \Magento\Customer\Api\Data\AddressInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressDataFactoryMock; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var AddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $address; + + /** + * @var JsonFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultJsonFactory; + + /** + * @var Json|\PHPUnit_Framework_MockObject_MockObject + */ + private $json; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->addressRepositoryMock = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); + $this->formFactoryMock = $this->createMock(\Magento\Customer\Model\Metadata\FormFactory::class); + $this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $this->dataObjectHelperMock = $this->createMock(\Magento\Framework\Api\DataObjectHelper ::class); + $this->addressDataFactoryMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterfaceFactory::class); + $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); + $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + $this->address = $this->getMockBuilder(AddressInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->resultJsonFactory = $this->getMockBuilder(JsonFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->json = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManagerHelper($this); + + $this->model = $objectManager->getObject( + \Magento\Customer\Controller\Adminhtml\Address\Save::class, + [ + 'addressRepository' => $this->addressRepositoryMock, + 'formFactory' => $this->formFactoryMock, + 'customerRepository' => $this->customerRepositoryMock, + 'dataObjectHelper' => $this->dataObjectHelperMock, + 'addressDataFactory' => $this->addressDataFactoryMock, + 'logger' => $this->loggerMock, + 'request' => $this->requestMock, + 'resultJsonFactory' => $this->resultJsonFactory + ] + ); + } + + public function testExecute(): void + { + $addressId = 11; + $customerId = 22; + + $addressExtractedData = [ + 'entity_id' => $addressId, + 'code' => 'value', + 'coolness' => false, + 'region' => 'region', + 'region_id' => 'region_id', + ]; + + $addressCompactedData = [ + 'entity_id' => $addressId, + 'default_billing' => 'true', + 'default_shipping' => 'true', + 'code' => 'value', + 'coolness' => false, + 'region' => 'region', + 'region_id' => 'region_id', + ]; + + $mergedAddressData = [ + 'entity_id' => $addressId, + 'default_billing' => true, + 'default_shipping' => true, + 'code' => 'value', + 'region' => [ + 'region' => 'region', + 'region_id' => 'region_id', + ], + 'region_id' => 'region_id', + 'id' => $addressId, + ]; + + $this->requestMock->method('getParam') + ->withConsecutive(['parent_id'], ['entity_id']) + ->willReturnOnConsecutiveCalls(22, 1); + + $customerMock = $this->getMockBuilder( + \Magento\Customer\Api\Data\CustomerInterface::class + )->disableOriginalConstructor()->getMock(); + + $this->customerRepositoryMock->expects($this->atLeastOnce()) + ->method('getById') + ->with($customerId) + ->willReturn($customerMock); + + $customerAddressFormMock = $this->createMock(\Magento\Customer\Model\Metadata\Form::class); + $customerAddressFormMock->expects($this->atLeastOnce()) + ->method('extractData') + ->with($this->requestMock) + ->willReturn($addressExtractedData); + $customerAddressFormMock->expects($this->once()) + ->method('compactData') + ->with($addressExtractedData) + ->willReturn($addressCompactedData); + + $this->formFactoryMock->expects($this->exactly(1)) + ->method('create') + ->willReturn($customerAddressFormMock); + + $addressMock = $this->getMockBuilder(AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->addressDataFactoryMock->expects($this->once())->method('create')->willReturn($addressMock); + + $this->dataObjectHelperMock->expects($this->atLeastOnce()) + ->method('populateWithArray') + ->willReturn( + [ + $addressMock, + $mergedAddressData, AddressInterface::class, + $this->dataObjectHelperMock, + ] + ); + $this->addressRepositoryMock->expects($this->once())->method('save')->willReturn($this->address); + $this->address->expects($this->once())->method('getId')->willReturn($addressId); + + $this->resultJsonFactory->expects($this->once()) + ->method('create') + ->willReturn($this->json); + $this->json->expects($this->once()) + ->method('setData') + ->with( + [ + 'message' => __('Customer address has been updated.'), + 'error' => false, + 'data' => [ + 'entity_id' => $addressId + ] + ] + )->willReturnSelf(); + + $this->assertEquals($this->json, $this->model->execute()); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/ValidateTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/ValidateTest.php new file mode 100644 index 0000000000000..a724bdd24959b --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/ValidateTest.php @@ -0,0 +1,118 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Controller\Address; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ValidateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Customer\Controller\Adminhtml\Address\Validate + */ + private $model; + + /** + * @var \Magento\Customer\Model\Metadata\FormFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $formFactoryMock; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \Magento\Backend\Model\View\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var \Magento\Framework\Controller\Result\JsonFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultJsonFactoryMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->formFactoryMock = $this->createMock(\Magento\Customer\Model\Metadata\FormFactory::class); + $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + $this->resultJsonFactoryMock = $this->createMock(\Magento\Framework\Controller\Result\JsonFactory::class); + $this->resultRedirectFactoryMock = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); + + $objectManager = new ObjectManagerHelper($this); + + $this->model = $objectManager->getObject( + \Magento\Customer\Controller\Adminhtml\Address\Validate::class, + [ + 'formFactory' => $this->formFactoryMock, + 'request' => $this->requestMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock, + 'resultJsonFactory' => $this->resultJsonFactoryMock, + ] + ); + } + + /** + * Test method \Magento\Customer\Controller\Adminhtml\Address\Save::execute + * + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testExecute() + { + $addressId = 11; + $errors = ['Error Message 1', 'Error Message 2']; + + $addressExtractedData = [ + 'entity_id' => $addressId, + 'default_billing' => true, + 'default_shipping' => true, + 'code' => 'value', + 'region' => [ + 'region' => 'region', + 'region_id' => 'region_id', + ], + 'region_id' => 'region_id', + 'id' => $addressId, + ]; + + $customerAddressFormMock = $this->createMock(\Magento\Customer\Model\Metadata\Form::class); + + $customerAddressFormMock->expects($this->atLeastOnce()) + ->method('extractData') + ->with($this->requestMock) + ->willReturn($addressExtractedData); + $customerAddressFormMock->expects($this->once()) + ->method('validateData') + ->with($addressExtractedData) + ->willReturn($errors); + + $this->formFactoryMock->expects($this->exactly(1)) + ->method('create') + ->willReturn($customerAddressFormMock); + + $resultJson = $this->createMock(\Magento\Framework\Controller\Result\Json::class); + $this->resultJsonFactoryMock->method('create') + ->willReturn($resultJson); + + $validateResponseMock = $this->createPartialMock( + \Magento\Framework\DataObject::class, + ['getError', 'setMessages'] + ); + $validateResponseMock->method('setMessages')->willReturnSelf(); + $validateResponseMock->method('getError')->willReturn(1); + + $resultJson->method('setData')->willReturnSelf(); + + $this->assertEquals($resultJson, $this->model->execute()); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/File/Address/UploadTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/File/Address/UploadTest.php index 20177ab0b0db6..8f8ed0e37a46b 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/File/Address/UploadTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/File/Address/UploadTest.php @@ -70,7 +70,8 @@ protected function setUp() $this->context, $this->fileUploaderFactory, $this->addressMetadataService, - $this->logger + $this->logger, + 'address' ); } @@ -104,27 +105,27 @@ public function testExecuteEmptyFiles() public function testExecute() { - $attributeCode = 'attribute_code'; + $attributeCode = 'file_address_attribute'; + $resultFileSize = 20000; + $resultFileName = 'text.txt'; + $resultType = 'text/plain'; $_FILES = [ - 'address' => [ - 'name' => [ - 'new_0' => [ - $attributeCode => 'filename', - ], - ], + $attributeCode => [ + 'name' => $resultFileName, + 'type' => $resultType, + 'size' => $resultFileSize ], ]; - $resultFileName = '/filename.ext1'; $resultFilePath = 'filepath'; $resultFileUrl = 'viewFileUrl'; $result = [ 'name' => $resultFileName, - 'file' => $resultFileName, - 'path' => $resultFilePath, - 'tmp_name' => $resultFilePath . $resultFileName, + 'type' => $resultType, + 'size' => $resultFileSize, + 'tmp_name' => $resultFilePath . '/' . $resultFileName, 'url' => $resultFileUrl, ]; @@ -173,15 +174,16 @@ public function testExecute() public function testExecuteWithErrors() { - $attributeCode = 'attribute_code'; + $attributeCode = 'file_address_attribute'; + $resultFileSize = 20000; + $resultFileName = 'text.txt'; + $resultType = 'text/plain'; $_FILES = [ - 'address' => [ - 'name' => [ - 'new_0' => [ - $attributeCode => 'filename', - ], - ], + $attributeCode => [ + 'name' => $resultFileName, + 'type' => $resultType, + 'size' => $resultFileSize ], ]; diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index 5372bb11a89b5..c52d5b2fb370f 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -5,7 +5,6 @@ */ namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; -use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerMetadataInterface; use Magento\Customer\Api\Data\AttributeMetadataInterface; use Magento\Customer\Api\Data\CustomerInterface; @@ -281,7 +280,6 @@ protected function setUp() public function testExecuteWithExistentCustomer() { $customerId = 22; - $addressId = 11; $subscription = 'true'; $postValue = [ 'customer' => [ @@ -290,18 +288,6 @@ public function testExecuteWithExistentCustomer() 'coolness' => false, 'disable_auto_group_change' => 'false', ], - 'address' => [ - '_template_' => '_template_', - $addressId => [ - 'entity_id' => $addressId, - 'default_billing' => 'true', - 'default_shipping' => 'true', - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ], - ], 'subscription' => $subscription, ]; $extractedData = [ @@ -318,22 +304,6 @@ public function testExecuteWithExistentCustomer() CustomerInterface::DEFAULT_BILLING => 2, CustomerInterface::DEFAULT_SHIPPING => 2 ]; - $addressExtractedData = [ - 'entity_id' => $addressId, - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ]; - $addressCompactedData = [ - 'entity_id' => $addressId, - 'default_billing' => 'true', - 'default_shipping' => 'true', - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ]; $savedData = [ 'entity_id' => $customerId, 'darkness' => true, @@ -341,61 +311,40 @@ public function testExecuteWithExistentCustomer() CustomerInterface::DEFAULT_BILLING => false, CustomerInterface::DEFAULT_SHIPPING => false, ]; - $savedAddressData = [ - 'entity_id' => $addressId, - 'default_billing' => true, - 'default_shipping' => true, - ]; $mergedData = [ 'entity_id' => $customerId, 'darkness' => true, 'name' => 'Name', 'code' => 'value', 'disable_auto_group_change' => 0, - CustomerInterface::DEFAULT_BILLING => $addressId, - CustomerInterface::DEFAULT_SHIPPING => $addressId, 'confirmation' => false, 'sendemail_store_id' => '1', 'id' => $customerId, ]; - $mergedAddressData = [ - 'entity_id' => $addressId, - 'default_billing' => true, - 'default_shipping' => true, - 'code' => 'value', - 'region' => [ - 'region' => 'region', - 'region_id' => 'region_id', - ], - 'region_id' => 'region_id', - 'id' => $addressId, - ]; /** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */ $attributeMock = $this->getMockBuilder( \Magento\Customer\Api\Data\AttributeMetadataInterface::class )->disableOriginalConstructor()->getMock(); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->atLeastOnce()) ->method('getAttributeCode') ->willReturn('coolness'); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->atLeastOnce()) ->method('getFrontendInput') ->willReturn('int'); $attributes = [$attributeMock]; - $this->requestMock->expects($this->any()) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPostValue') ->willReturnMap([ [null, null, $postValue], [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ['address/' . $addressId, null, $postValue['address'][$addressId]], ]); - $this->requestMock->expects($this->exactly(3)) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address', null, $postValue['address']], ['subscription', null, $subscription], ] ); @@ -404,16 +353,15 @@ public function testExecuteWithExistentCustomer() $objectMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->disableOriginalConstructor() ->getMock(); - $objectMock->expects($this->exactly(2)) + $objectMock->expects($this->atLeastOnce()) ->method('getData') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address/' . $addressId, null, $postValue['address'][$addressId]], ] ); - $this->objectFactoryMock->expects($this->exactly(2)) + $this->objectFactoryMock->expects($this->exactly(1)) ->method('create') ->with(['data' => $postValue]) ->willReturn($objectMock); @@ -432,23 +380,7 @@ public function testExecuteWithExistentCustomer() $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); - - $customerAddressFormMock = $this->getMockBuilder( - \Magento\Customer\Model\Metadata\Form::class - )->disableOriginalConstructor()->getMock(); - $customerAddressFormMock->expects($this->once()) - ->method('extractData') - ->with($this->requestMock, 'address/' . $addressId) - ->willReturn($addressExtractedData); - $customerAddressFormMock->expects($this->once()) - ->method('compactData') - ->with($addressExtractedData) - ->willReturn($addressCompactedData); - $customerAddressFormMock->expects($this->once()) - ->method('getAttributes') - ->willReturn($attributes); - - $this->formFactoryMock->expects($this->exactly(2)) + $this->formFactoryMock->expects($this->exactly(1)) ->method('create') ->willReturnMap( [ @@ -461,15 +393,6 @@ public function testExecuteWithExistentCustomer() [], $customerFormMock ], - [ - AddressMetadataInterface::ENTITY_TYPE_ADDRESS, - 'adminhtml_customer_address', - $savedAddressData, - false, - Form::DONT_IGNORE_INVISIBLE, - [], - $customerAddressFormMock - ], ] ); @@ -492,25 +415,7 @@ public function testExecuteWithExistentCustomer() ->with($customerMock) ->willReturn($savedData); - $addressMock = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerAddressRepositoryMock->expects($this->once()) - ->method('getById') - ->with($addressId) - ->willReturn($addressMock); - - $this->customerAddressMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($addressMock) - ->willReturn($savedAddressData); - - $this->addressDataFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($addressMock); - - $this->dataHelperMock->expects($this->exactly(2)) + $this->dataHelperMock->expects($this->atLeastOnce()) ->method('populateWithArray') ->willReturnMap( [ @@ -519,19 +424,9 @@ public function testExecuteWithExistentCustomer() $mergedData, \Magento\Customer\Api\Data\CustomerInterface::class, $this->dataHelperMock ], - [ - $addressMock, - $mergedAddressData, \Magento\Customer\Api\Data\AddressInterface::class, - $this->dataHelperMock - ], ] ); - $customerMock->expects($this->once()) - ->method('setAddresses') - ->with([$addressMock]) - ->willReturnSelf(); - $this->customerRepositoryMock->expects($this->once()) ->method('save') ->with($customerMock) @@ -608,63 +503,33 @@ public function testExecuteWithExistentCustomer() public function testExecuteWithNewCustomer() { $customerId = 22; - $addressId = 11; + $subscription = '0'; $postValue = [ 'customer' => [ 'coolness' => false, 'disable_auto_group_change' => 'false', ], - 'address' => [ - '_template_' => '_template_', - $addressId => [ - 'entity_id' => $addressId, - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ], - ], 'subscription' => $subscription, ]; $extractedData = [ 'coolness' => false, 'disable_auto_group_change' => 'false', ]; - $addressExtractedData = [ - 'entity_id' => $addressId, - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ]; $mergedData = [ 'disable_auto_group_change' => 0, CustomerInterface::DEFAULT_BILLING => null, CustomerInterface::DEFAULT_SHIPPING => null, 'confirmation' => false, ]; - $mergedAddressData = [ - 'entity_id' => $addressId, - 'default_billing' => false, - 'default_shipping' => false, - 'code' => 'value', - 'region' => [ - 'region' => 'region', - 'region_id' => 'region_id', - ], - 'region_id' => 'region_id', - 'id' => $addressId, - ]; - /** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */ $attributeMock = $this->getMockBuilder( \Magento\Customer\Api\Data\AttributeMetadataInterface::class )->disableOriginalConstructor()->getMock(); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->atLeastOnce()) ->method('getAttributeCode') ->willReturn('coolness'); - $attributeMock->expects($this->exactly(2)) + $attributeMock->expects($this->atLeastOnce()) ->method('getFrontendInput') ->willReturn('int'); $attributes = [$attributeMock]; @@ -674,14 +539,12 @@ public function testExecuteWithNewCustomer() ->willReturnMap([ [null, null, $postValue], [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ['address/' . $addressId, null, $postValue['address'][$addressId]], ]); - $this->requestMock->expects($this->exactly(3)) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address', null, $postValue['address']], ['subscription', null, $subscription], ] ); @@ -690,16 +553,15 @@ public function testExecuteWithNewCustomer() $objectMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->disableOriginalConstructor() ->getMock(); - $objectMock->expects($this->exactly(2)) + $objectMock->expects($this->atLeastOnce()) ->method('getData') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address/' . $addressId, null, $postValue['address'][$addressId]], ] ); - $this->objectFactoryMock->expects($this->exactly(2)) + $this->objectFactoryMock->expects($this->atLeastOnce()) ->method('create') ->with(['data' => $postValue]) ->willReturn($objectMock); @@ -719,22 +581,7 @@ public function testExecuteWithNewCustomer() ->method('getAttributes') ->willReturn($attributes); - $customerAddressFormMock = $this->getMockBuilder( - \Magento\Customer\Model\Metadata\Form::class - )->disableOriginalConstructor()->getMock(); - $customerAddressFormMock->expects($this->once()) - ->method('extractData') - ->with($this->requestMock, 'address/' . $addressId) - ->willReturn($addressExtractedData); - $customerAddressFormMock->expects($this->once()) - ->method('compactData') - ->with($addressExtractedData) - ->willReturn($addressExtractedData); - $customerAddressFormMock->expects($this->once()) - ->method('getAttributes') - ->willReturn($attributes); - - $this->formFactoryMock->expects($this->exactly(2)) + $this->formFactoryMock->expects($this->exactly(1)) ->method('create') ->willReturnMap( [ @@ -747,15 +594,6 @@ public function testExecuteWithNewCustomer() [], $customerFormMock ], - [ - AddressMetadataInterface::ENTITY_TYPE_ADDRESS, - 'adminhtml_customer_address', - [], - false, - Form::DONT_IGNORE_INVISIBLE, - [], - $customerAddressFormMock - ], ] ); @@ -768,25 +606,7 @@ public function testExecuteWithNewCustomer() ->method('create') ->willReturn($customerMock); - $addressMock = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->addressDataFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($addressMock); - - $this->customerAddressRepositoryMock->expects($this->once()) - ->method('getById') - ->with($addressId) - ->willReturn($addressMock); - - $this->customerAddressMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($addressMock) - ->willReturn([]); - - $this->dataHelperMock->expects($this->exactly(2)) + $this->dataHelperMock->expects($this->atLeastOnce()) ->method('populateWithArray') ->willReturnMap( [ @@ -795,11 +615,6 @@ public function testExecuteWithNewCustomer() $mergedData, \Magento\Customer\Api\Data\CustomerInterface::class, $this->dataHelperMock ], - [ - $addressMock, - $mergedAddressData, \Magento\Customer\Api\Data\AddressInterface::class, - $this->dataHelperMock - ], ] ); @@ -904,12 +719,11 @@ public function testExecuteWithNewCustomerAndValidationException() [null, null, $postValue], [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], ]); - $this->requestMock->expects($this->exactly(2)) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address', null, null], ] ); @@ -1047,12 +861,11 @@ public function testExecuteWithNewCustomerAndLocalizedException() [null, null, $postValue], [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], ]); - $this->requestMock->expects($this->exactly(2)) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address', null, null], ] ); @@ -1190,12 +1003,11 @@ public function testExecuteWithNewCustomerAndException() [null, null, $postValue], [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], ]); - $this->requestMock->expects($this->exactly(2)) + $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( [ ['customer', null, $postValue['customer']], - ['address', null, null], ] ); diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ValidateTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ValidateTest.php index 7209ac9fd24b0..5adb902601630 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ValidateTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ValidateTest.php @@ -141,12 +141,6 @@ protected function setUp() public function testExecute() { - $this->request->expects($this->once()) - ->method('getPost') - ->willReturn([ - '_template_' => null, - 'address_index' => null - ]); $customerEntityId = 2; $this->request->expects($this->once()) ->method('getParam') @@ -162,11 +156,6 @@ public function testExecute() $this->form->expects($this->once())->method('setInvisibleIgnored'); $this->form->expects($this->atLeastOnce())->method('extractData')->willReturn([]); - $error = $this->createMock(\Magento\Framework\Message\Error::class); - $this->form->expects($this->once()) - ->method('validateData') - ->willReturn([$error]); - $validationResult = $this->getMockForAbstractClass( \Magento\Customer\Api\Data\ValidationResultsInterface::class, [], @@ -188,9 +177,6 @@ public function testExecute() public function testExecuteWithoutAddresses() { - $this->request->expects($this->once()) - ->method('getPost') - ->willReturn(null); $this->form->expects($this->once()) ->method('setInvisibleIgnored'); $this->form->expects($this->atLeastOnce()) @@ -223,9 +209,6 @@ public function testExecuteWithoutAddresses() public function testExecuteWithException() { - $this->request->expects($this->once()) - ->method('getPost') - ->willReturn(null); $this->form->expects($this->once()) ->method('setInvisibleIgnored'); $this->form->expects($this->atLeastOnce()) @@ -265,12 +248,6 @@ public function testExecuteWithException() public function testExecuteWithNewCustomerAndNoEntityId() { - $this->request->expects($this->once()) - ->method('getPost') - ->willReturn([ - '_template_' => null, - 'address_index' => null - ]); $this->request->expects($this->once()) ->method('getParam') ->with('customer') @@ -282,11 +259,6 @@ public function testExecuteWithNewCustomerAndNoEntityId() $this->form->expects($this->once())->method('setInvisibleIgnored'); $this->form->expects($this->atLeastOnce())->method('extractData')->willReturn([]); - $error = $this->createMock(\Magento\Framework\Message\Error::class); - $this->form->expects($this->once()) - ->method('validateData') - ->willReturn([$error]); - $validationResult = $this->getMockForAbstractClass( \Magento\Customer\Api\Data\ValidationResultsInterface::class, [], diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Ajax/LoginTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Ajax/LoginTest.php index 628727b202ba7..14ed09f73325b 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Ajax/LoginTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Ajax/LoginTest.php @@ -260,14 +260,10 @@ public function testLoginFailure(): void ->with($customer); $this->customerSession->expects(self::never()) ->method('regenerateId'); - $this->customerSession->method('getData') - ->with('user_login_show_captcha') - ->willReturn(false); $result = [ 'errors' => true, - 'message' => __('Invalid login or password.'), - 'captcha' => false + 'message' => __('Invalid login or password.') ]; $this->resultJson->method('setData') ->with($result) diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php new file mode 100644 index 0000000000000..0273c445bdd2a --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -0,0 +1,2120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Model; + +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\AccountConfirmation; +use Magento\Customer\Model\AccountManagement; +use Magento\Customer\Model\AuthenticationInterface; +use Magento\Customer\Model\Data\Customer; +use Magento\Customer\Model\EmailNotificationInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Area; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Intl\DateTimeFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Store\Model\ScopeInterface; + +/** + * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AccountManagementTest extends \PHPUnit\Framework\TestCase +{ + /** @var AccountManagement */ + protected $accountManagement; + + /** @var ObjectManagerHelper */ + protected $objectManagerHelper; + + /** @var \Magento\Customer\Model\CustomerFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $customerFactory; + + /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $manager; + + /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $storeManager; + + /** @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject */ + protected $random; + + /** @var \Magento\Customer\Model\Metadata\Validator|\PHPUnit_Framework_MockObject_MockObject */ + protected $validator; + + /** @var \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $validationResultsInterfaceFactory; + + /** @var \Magento\Customer\Api\AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $addressRepository; + + /** @var \Magento\Customer\Api\CustomerMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $customerMetadata; + + /** @var \Magento\Customer\Model\CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject */ + protected $customerRegistry; + + /** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $logger; + + /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $encryptor; + + /** @var \Magento\Customer\Model\Config\Share|\PHPUnit_Framework_MockObject_MockObject */ + protected $share; + + /** @var \Magento\Framework\Stdlib\StringUtils|\PHPUnit_Framework_MockObject_MockObject */ + protected $string; + + /** @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $customerRepository; + + /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $scopeConfig; + + /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ + protected $transportBuilder; + + /** @var \Magento\Framework\Reflection\DataObjectProcessor|\PHPUnit_Framework_MockObject_MockObject */ + protected $dataObjectProcessor; + + /** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject */ + protected $registry; + + /** @var \Magento\Customer\Helper\View|\PHPUnit_Framework_MockObject_MockObject */ + protected $customerViewHelper; + + /** @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject */ + protected $dateTime; + + /** @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject */ + protected $customer; + + /** @var \Magento\Framework\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $objectFactory; + + /** @var \Magento\Framework\Api\ExtensibleDataObjectConverter|\PHPUnit_Framework_MockObject_MockObject */ + protected $extensibleDataObjectConverter; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\Store + */ + protected $store; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\Data\CustomerSecure + */ + protected $customerSecure; + + /** + * @var AuthenticationInterface |\PHPUnit_Framework_MockObject_MockObject + */ + protected $authenticationMock; + + /** + * @var EmailNotificationInterface |\PHPUnit_Framework_MockObject_MockObject + */ + protected $emailNotificationMock; + + /** + * @var DateTimeFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $dateTimeFactory; + + /** + * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject + */ + private $accountConfirmation; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SessionManagerInterface + */ + private $sessionManager; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory + */ + private $visitorCollectionFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SaveHandlerInterface + */ + private $saveHandler; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\AddressRegistry + */ + private $addressRegistryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|SearchCriteriaBuilder + */ + private $searchCriteriaBuilderMock; + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function setUp() + { + $this->customerFactory = $this->createPartialMock(\Magento\Customer\Model\CustomerFactory::class, ['create']); + $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + $this->store = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->random = $this->createMock(\Magento\Framework\Math\Random::class); + $this->validator = $this->createMock(\Magento\Customer\Model\Metadata\Validator::class); + $this->validationResultsInterfaceFactory = $this->createMock( + \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory::class + ); + $this->addressRepository = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); + $this->customerMetadata = $this->createMock(\Magento\Customer\Api\CustomerMetadataInterface::class); + $this->customerRegistry = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); + $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); + $this->encryptor = $this->createMock(\Magento\Framework\Encryption\EncryptorInterface::class); + $this->share = $this->createMock(\Magento\Customer\Model\Config\Share::class); + $this->string = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); + $this->customerRepository = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->transportBuilder = $this->createMock(\Magento\Framework\Mail\Template\TransportBuilder::class); + $this->dataObjectProcessor = $this->createMock(\Magento\Framework\Reflection\DataObjectProcessor::class); + $this->registry = $this->createMock(\Magento\Framework\Registry::class); + $this->customerViewHelper = $this->createMock(\Magento\Customer\Helper\View::class); + $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); + $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); + $this->objectFactory = $this->createMock(\Magento\Framework\DataObjectFactory::class); + $this->addressRegistryMock = $this->createMock(\Magento\Customer\Model\AddressRegistry::class); + $this->extensibleDataObjectConverter = $this->createMock( + \Magento\Framework\Api\ExtensibleDataObjectConverter::class + ); + $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->emailNotificationMock = $this->getMockBuilder(EmailNotificationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->setMethods(['setRpToken', 'addData', 'setRpTokenCreatedAt', 'setData']) + ->disableOriginalConstructor() + ->getMock(); + + $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); + $this->accountConfirmation = $this->createMock(AccountConfirmation::class); + $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); + + $this->visitorCollectionFactory = $this->getMockBuilder( + \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->accountManagement = $this->objectManagerHelper->getObject( + \Magento\Customer\Model\AccountManagement::class, + [ + 'customerFactory' => $this->customerFactory, + 'eventManager' => $this->manager, + 'storeManager' => $this->storeManager, + 'mathRandom' => $this->random, + 'validator' => $this->validator, + 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, + 'addressRepository' => $this->addressRepository, + 'customerMetadataService' => $this->customerMetadata, + 'customerRegistry' => $this->customerRegistry, + 'logger' => $this->logger, + 'encryptor' => $this->encryptor, + 'configShare' => $this->share, + 'stringHelper' => $this->string, + 'customerRepository' => $this->customerRepository, + 'scopeConfig' => $this->scopeConfig, + 'transportBuilder' => $this->transportBuilder, + 'dataProcessor' => $this->dataObjectProcessor, + 'registry' => $this->registry, + 'customerViewHelper' => $this->customerViewHelper, + 'dateTime' => $this->dateTime, + 'customerModel' => $this->customer, + 'objectFactory' => $this->objectFactory, + 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, + 'dateTimeFactory' => $this->dateTimeFactory, + 'accountConfirmation' => $this->accountConfirmation, + 'sessionManager' => $this->sessionManager, + 'saveHandler' => $this->saveHandler, + 'visitorCollectionFactory' => $this->visitorCollectionFactory, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, + 'addressRegistry' => $this->addressRegistryMock, + ] + ); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->accountManagement, + 'authentication', + $this->authenticationMock + ); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->accountManagement, + 'emailNotification', + $this->emailNotificationMock + ); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testCreateAccountWithPasswordHashWithExistingCustomer() + { + $websiteId = 1; + $storeId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->once()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->once()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager + ->expects($this->once()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->accountManagement->createAccountWithPasswordHash($customer, $hash); + } + + /** + * @expectedException \Magento\Framework\Exception\State\InputMismatchException + */ + public function testCreateAccountWithPasswordHashWithCustomerWithoutStoreId() + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->atLeastOnce()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->once()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer + ->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer + ->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share + ->expects($this->atLeastOnce()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $exception = new \Magento\Framework\Exception\AlreadyExistsException( + new \Magento\Framework\Phrase('Exception message') + ); + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customer, $hash) + ->willThrowException($exception); + + $this->accountManagement->createAccountWithPasswordHash($customer, $hash); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithPasswordHashWithLocalizedException() + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->once()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer + ->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer + ->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $exception = new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase('Exception message') + ); + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customer, $hash) + ->willThrowException($exception); + + $this->accountManagement->createAccountWithPasswordHash($customer, $hash); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithPasswordHashWithAddressException() + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $address->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->once()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer + ->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer + ->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customer, $hash) + ->willReturn($customer); + $exception = new \Magento\Framework\Exception\InputException( + new \Magento\Framework\Phrase('Exception message') + ); + $this->addressRepository + ->expects($this->atLeastOnce()) + ->method('save') + ->with($address) + ->willThrowException($exception); + $this->customerRepository + ->expects($this->once()) + ->method('delete') + ->with($customer); + + $this->accountManagement->createAccountWithPasswordHash($customer, $hash); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedException() + { + $storeId = 1; + $storeName = 'store_name'; + $websiteId = 1; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + + $customerMock->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(null); + $customerMock->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customerMock->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customerMock->expects($this->once()) + ->method('setCreatedIn') + ->with($storeName) + ->willReturnSelf(); + $customerMock->expects($this->once()) + ->method('getAddresses') + ->willReturn([]); + $customerMock->expects($this->once()) + ->method('setAddresses') + ->with(null) + ->willReturnSelf(); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + + $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); + + $storeMock->expects($this->once()) + ->method('getName') + ->willReturn($storeName); + + $this->storeManager->expects($this->exactly(1)) + ->method('getStore') + ->with($storeId) + ->willReturn($storeMock); + $exception = new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase('Exception message') + ); + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customerMock, $hash) + ->willThrowException($exception); + + $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateAccountWithoutPassword() + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; + + $datetime = $this->prepareDateTimeFactory(); + + $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $address->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->atLeastOnce()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->atLeastOnce()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->customerRepository->expects($this->atLeastOnce()) + ->method('save') + ->willReturn($customer); + $this->addressRepository->expects($this->atLeastOnce()) + ->method('save') + ->with($address); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($newLinkToken); + $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) + ->disableOriginalConstructor() + ->getMock(); + $customerSecure->expects($this->any()) + ->method('setRpToken') + ->with($newLinkToken); + $customerSecure->expects($this->any()) + ->method('setRpTokenCreatedAt') + ->with($datetime) + ->willReturnSelf(); + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn(null); + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($customerSecure); + $this->emailNotificationMock->expects($this->once()) + ->method('newAccount') + ->willReturnSelf(); + + $this->accountManagement->createAccount($customer); + } + + /** + * Data provider for testCreateAccountWithPasswordInputException test + * + * @return array + */ + public function dataProviderCheckPasswordStrength() + { + return [ + [ + 'testNumber' => 1, + 'password' => 'qwer', + 'minPasswordLength' => 5, + 'minCharacterSetsNum' => 1, + ], + [ + 'testNumber' => 2, + 'password' => 'wrfewqedf1', + 'minPasswordLength' => 5, + 'minCharacterSetsNum' => 3, + ], + ]; + } + + /** + * @param int $testNumber + * @param string $password + * @param int $minPasswordLength + * @param int $minCharacterSetsNum + * @dataProvider dataProviderCheckPasswordStrength + */ + public function testCreateAccountWithPasswordInputException( + $testNumber, + $password, + $minPasswordLength, + $minCharacterSetsNum + ) { + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->will( + $this->returnValueMap( + [ + [ + AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, + 'default', + null, + $minPasswordLength, + ], + [ + AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, + 'default', + null, + $minCharacterSetsNum, + ], + ] + ) + ); + + $this->string->expects($this->any()) + ->method('strlen') + ->with($password) + ->willReturn(iconv_strlen($password, 'UTF-8')); + + if ($testNumber == 1) { + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->expectExceptionMessage('The password needs at least ' . $minPasswordLength . ' characters. ' + . 'Create a new password and try again.'); + } + + if ($testNumber == 2) { + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->expectExceptionMessage('Minimum of different classes of characters in password is ' . + $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.'); + } + + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $this->accountManagement->createAccount($customer, $password); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountInputExceptionExtraLongPassword() + { + $password = '257*chars*************************************************************************************' + . '****************************************************************************************************' + . '***************************************************************'; + + $this->string->expects($this->any()) + ->method('strlen') + ->with($password) + ->willReturn(iconv_strlen($password, 'UTF-8')); + + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->expectExceptionMessage('Please enter a password with at most 256 characters.'); + + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $this->accountManagement->createAccount($customer, $password); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateAccountWithPassword() + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + $password = 'wrfewqedf1'; + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + + $datetime = $this->prepareDateTimeFactory(); + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->willReturnMap( + [ + [ + AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, + 'default', + null, + $minPasswordLength, + ], + [ + AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, + 'default', + null, + $minCharacterSetsNum, + ], + [ + AccountManagement::XML_PATH_REGISTER_EMAIL_TEMPLATE, + ScopeInterface::SCOPE_STORE, + $defaultStoreId, + $templateIdentifier, + ], + [ + AccountManagement::XML_PATH_REGISTER_EMAIL_IDENTITY, + ScopeInterface::SCOPE_STORE, + 1, + $sender, + ], + ] + ); + $this->string->expects($this->any()) + ->method('strlen') + ->with($password) + ->willReturn(iconv_strlen($password, 'UTF-8')); + $this->encryptor->expects($this->once()) + ->method('getHash') + ->with($password, true) + ->willReturn($hash); + $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $address->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->atLeastOnce()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->atLeastOnce()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->customerRepository->expects($this->atLeastOnce()) + ->method('save') + ->willReturn($customer); + $this->addressRepository->expects($this->atLeastOnce()) + ->method('save') + ->with($address); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($newLinkToken); + $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) + ->disableOriginalConstructor() + ->getMock(); + $customerSecure->expects($this->any()) + ->method('setRpToken') + ->with($newLinkToken); + $customerSecure->expects($this->any()) + ->method('setRpTokenCreatedAt') + ->with($datetime) + ->willReturnSelf(); + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn($hash); + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($customerSecure); + $this->emailNotificationMock->expects($this->once()) + ->method('newAccount') + ->willReturnSelf(); + + $this->accountManagement->createAccount($customer, $password); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testSendPasswordReminderEmail() + { + $customerId = 1; + $customerStoreId = 2; + $customerEmail = 'email@email.com'; + $customerData = ['key' => 'value']; + $customerName = 'Customer Name'; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + + $customer = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customer->expects($this->any()) + ->method('getStoreId') + ->willReturn($customerStoreId); + $customer->expects($this->any()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->any()) + ->method('getEmail') + ->willReturn($customerEmail); + + $this->store->expects($this->any()) + ->method('getId') + ->willReturn($customerStoreId); + + $this->storeManager->expects($this->at(0)) + ->method('getStore') + ->willReturn($this->store); + + $this->storeManager->expects($this->at(1)) + ->method('getStore') + ->with($customerStoreId) + ->willReturn($this->store); + + $this->customerRegistry->expects($this->once()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($this->customerSecure); + + $this->dataObjectProcessor->expects($this->once()) + ->method('buildOutputDataArray') + ->with($customer, CustomerInterface::class) + ->willReturn($customerData); + + $this->customerViewHelper->expects($this->any()) + ->method('getCustomerName') + ->with($customer) + ->willReturn($customerName); + + $this->customerSecure->expects($this->once()) + ->method('addData') + ->with($customerData) + ->willReturnSelf(); + $this->customerSecure->expects($this->once()) + ->method('setData') + ->with('name', $customerName) + ->willReturnSelf(); + + $this->scopeConfig->expects($this->at(0)) + ->method('getValue') + ->with(AccountManagement::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, $customerStoreId) + ->willReturn($templateIdentifier); + $this->scopeConfig->expects($this->at(1)) + ->method('getValue') + ->with(AccountManagement::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) + ->willReturn($sender); + + $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) + ->getMock(); + + $this->transportBuilder->expects($this->once()) + ->method('setTemplateIdentifier') + ->with($templateIdentifier) + ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setTemplateOptions') + ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) + ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setTemplateVars') + ->with(['customer' => $this->customerSecure, 'store' => $this->store]) + ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setFrom') + ->with($sender) + ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('addTo') + ->with($customerEmail, $customerName) + ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('getTransport') + ->willReturn($transport); + + $transport->expects($this->once()) + ->method('sendMessage'); + + $this->assertEquals($this->accountManagement, $this->accountManagement->sendPasswordReminderEmail($customer)); + } + + /** + * @param string $email + * @param string $templateIdentifier + * @param string $sender + * @param int $storeId + * @param int $customerId + * @param string $hash + */ + protected function prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash) + { + $websiteId = 1; + $addressId = 5; + $datetime = $this->prepareDateTimeFactory(); + $customerData = ['key' => 'value']; + $customerName = 'Customer Name'; + + $this->store->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $this->store->expects($this->any()) + ->method('getId') + ->willReturn($storeId); + $this->storeManager->expects($this->any()) + ->method('getStore') + ->willReturn($this->store); + + /** @var \Magento\Customer\Model\Address|\PHPUnit_Framework_MockObject_MockObject $addressModel */ + $addressModel = $this->getMockBuilder(\Magento\Customer\Model\Address::class)->disableOriginalConstructor() + ->setMethods(['setShouldIgnoreValidation'])->getMock(); + + /** @var \Magento\Customer\Api\Data\AddressInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->once()) + ->method('getId') + ->willReturn($addressId); + + /** @var Customer|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customer->expects($this->any()) + ->method('getEmail') + ->willReturn($email); + $customer->expects($this->any()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->any()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->any()) + ->method('getAddresses') + ->willReturn([$address]); + $this->customerRepository->expects($this->once()) + ->method('get') + ->willReturn($customer); + $this->addressRegistryMock->expects($this->once()) + ->method('retrieve') + ->with($addressId) + ->willReturn($addressModel); + $addressModel->expects($this->once()) + ->method('setShouldIgnoreValidation') + ->with(true); + $this->customerRepository->expects($this->once()) + ->method('get') + ->with($email, $websiteId) + ->willReturn($customer); + $this->customerRepository->expects($this->once()) + ->method('save') + ->with($customer) + ->willReturnSelf(); + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($hash); + $this->customerViewHelper->expects($this->any()) + ->method('getCustomerName') + ->with($customer) + ->willReturn($customerName); + $this->customerSecure->expects($this->any()) + ->method('setRpToken') + ->with($hash) + ->willReturnSelf(); + $this->customerSecure->expects($this->any()) + ->method('setRpTokenCreatedAt') + ->with($datetime) + ->willReturnSelf(); + $this->customerSecure->expects($this->any()) + ->method('addData') + ->with($customerData) + ->willReturnSelf(); + $this->customerSecure->expects($this->any()) + ->method('setData') + ->with('name', $customerName) + ->willReturnSelf(); + $this->customerRegistry->expects($this->any()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($this->customerSecure); + $this->dataObjectProcessor->expects($this->any()) + ->method('buildOutputDataArray') + ->with($customer, Customer::class) + ->willReturn($customerData); + + $this->prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName); + } + + /** + * @param string $email + * @param int $templateIdentifier + * @param string $sender + * @param int $storeId + * @param string $customerName + */ + protected function prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName) + { + $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) + ->getMock(); + + $this->transportBuilder->expects($this->any()) + ->method('setTemplateIdentifier') + ->with($templateIdentifier) + ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('setTemplateOptions') + ->with(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) + ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('setTemplateVars') + ->with(['customer' => $this->customerSecure, 'store' => $this->store]) + ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('setFrom') + ->with($sender) + ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('addTo') + ->with($email, $customerName) + ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('getTransport') + ->willReturn($transport); + + $transport->expects($this->any()) + ->method('sendMessage'); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testInitiatePasswordResetEmailReminder() + { + $customerId = 1; + + $email = 'test@example.com'; + $template = AccountManagement::EMAIL_REMINDER; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + + $storeId = 1; + + mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); + $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); + + $this->emailNotificationMock->expects($this->once()) + ->method('passwordReminder') + ->willReturnSelf(); + + $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); + + $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testInitiatePasswordResetEmailReset() + { + $storeId = 1; + $customerId = 1; + + $email = 'test@example.com'; + $template = AccountManagement::EMAIL_RESET; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + + mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); + $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); + + $this->emailNotificationMock->expects($this->once()) + ->method('passwordResetConfirmation') + ->willReturnSelf(); + + $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); + + $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testInitiatePasswordResetNoTemplate() + { + $storeId = 1; + $customerId = 1; + + $email = 'test@example.com'; + $template = null; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + + mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); + $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); + + $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); + + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->expectExceptionMessage( + 'Invalid value of "" provided for the template field. Possible values: email_reminder or email_reset.' + ); + $this->accountManagement->initiatePasswordReset($email, $template); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage Invalid value of "0" provided for the customerId field + */ + public function testValidateResetPasswordTokenBadCustomerId() + { + $this->accountManagement->validateResetPasswordLinkToken(0, ''); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage "resetPasswordLinkToken" is required. Enter and try again. + */ + public function testValidateResetPasswordTokenBadResetPasswordLinkToken() + { + $this->accountManagement->validateResetPasswordLinkToken(22, null); + } + + /** + * @expectedException \Magento\Framework\Exception\State\InputMismatchException + * @expectedExceptionMessage The password token is mismatched. Reset and try again. + */ + public function testValidateResetPasswordTokenTokenMismatch() + { + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($this->customerSecure); + + $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); + } + + /** + * @expectedException \Magento\Framework\Exception\State\ExpiredException + * @expectedExceptionMessage The password token is expired. Reset and try again. + */ + public function testValidateResetPasswordTokenTokenExpired() + { + $this->reInitModel(); + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($this->customerSecure); + + $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); + } + + /** + * return bool + */ + public function testValidateResetPasswordToken() + { + $this->reInitModel(); + + $this->customer + ->expects($this->once()) + ->method('getResetPasswordLinkExpirationPeriod') + ->willReturn(100000); + + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($this->customerSecure); + + $this->assertTrue($this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken')); + } + + /** + * reInit $this->accountManagement object + */ + private function reInitModel() + { + $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'getRpToken', + 'getRpTokenCreatedAt', + 'getPasswordHash', + 'setPasswordHash', + 'setRpToken', + 'setRpTokenCreatedAt', + ] + ) + ->getMock(); + $this->customerSecure->expects($this->any()) + ->method('getRpToken') + ->willReturn('newStringToken'); + $pastDateTime = '2016-10-25 00:00:00'; + $this->customerSecure->expects($this->any()) + ->method('getRpTokenCreatedAt') + ->willReturn($pastDateTime); + $this->customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + ->disableOriginalConstructor() + ->setMethods(['getResetPasswordLinkExpirationPeriod']) + ->getMock(); + + $this->prepareDateTimeFactory(); + $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->visitorCollectionFactory = $this->getMockBuilder( + \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class + ) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) + ->disableOriginalConstructor() + ->setMethods(['destroy']) + ->getMockForAbstractClass(); + + $dateTime = '2017-10-25 18:57:08'; + $timestamp = '1508983028'; + $dateTimeMock = $this->getMockBuilder(\DateTime::class) + ->disableOriginalConstructor() + ->setMethods(['format', 'getTimestamp', 'setTimestamp']) + ->getMock(); + + $dateTimeMock->expects($this->any()) + ->method('format') + ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) + ->willReturn($dateTime); + $dateTimeMock->expects($this->any()) + ->method('getTimestamp') + ->willReturn($timestamp); + $dateTimeMock->expects($this->any()) + ->method('setTimestamp') + ->willReturnSelf(); + $dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $dateTimeFactory->expects($this->any())->method('create')->willReturn($dateTimeMock); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->accountManagement = $this->objectManagerHelper->getObject( + \Magento\Customer\Model\AccountManagement::class, + [ + 'customerFactory' => $this->customerFactory, + 'customerRegistry' => $this->customerRegistry, + 'customerRepository' => $this->customerRepository, + 'customerModel' => $this->customer, + 'dateTimeFactory' => $dateTimeFactory, + 'stringHelper' => $this->string, + 'scopeConfig' => $this->scopeConfig, + 'sessionManager' => $this->sessionManager, + 'visitorCollectionFactory' => $this->visitorCollectionFactory, + 'saveHandler' => $this->saveHandler, + 'encryptor' => $this->encryptor, + 'dataProcessor' => $this->dataObjectProcessor, + 'storeManager' => $this->storeManager, + 'addressRegistry' => $this->addressRegistryMock, + 'transportBuilder' => $this->transportBuilder, + ] + ); + $this->objectManagerHelper->setBackwardCompatibleProperty( + $this->accountManagement, + 'authentication', + $this->authenticationMock + ); + } + + /** + * @return void + */ + public function testChangePassword() + { + $customerId = 7; + $email = 'test@example.com'; + $currentPassword = '1234567'; + $newPassword = 'abcdefg'; + $passwordHash = '1a2b3f4c'; + + $this->reInitModel(); + $customer = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customer->expects($this->any()) + ->method('getId') + ->willReturn($customerId); + + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($email) + ->willReturn($customer); + + $this->authenticationMock->expects($this->once()) + ->method('authenticate'); + + $this->customerSecure->expects($this->once()) + ->method('setRpToken') + ->with(null); + $this->customerSecure->expects($this->once()) + ->method('setRpTokenCreatedAt') + ->willReturnSelf(); + $this->customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn($passwordHash); + + $this->customerRegistry->expects($this->any()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($this->customerSecure); + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->willReturnMap( + [ + [ + AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, + 'default', + null, + 7, + ], + [ + AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, + 'default', + null, + 1, + ], + ] + ); + $this->string->expects($this->any()) + ->method('strlen') + ->with($newPassword) + ->willReturn(7); + + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customer); + + $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); + + $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) + ->disableOriginalConstructor() + ->setMethods(['getSessionId']) + ->getMock(); + $visitor->expects($this->atLeastOnce())->method('getSessionId') + ->willReturnOnConsecutiveCalls('session_id_1', 'session_id_2'); + $visitorCollection = $this->getMockBuilder( + \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class + ) + ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); + $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); + $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); + $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($visitorCollection); + $this->saveHandler->expects($this->atLeastOnce())->method('destroy') + ->withConsecutive( + ['session_id_1'], + ['session_id_2'] + ); + + $this->assertTrue($this->accountManagement->changePassword($email, $currentPassword, $newPassword)); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testResetPassword() + { + $customerEmail = 'customer@example.com'; + $customerId = '1'; + $addressId = 5; + $resetToken = 'newStringToken'; + $newPassword = 'new_password'; + + $this->reInitModel(); + /** @var \Magento\Customer\Model\Address|\PHPUnit_Framework_MockObject_MockObject $addressModel */ + $addressModel = $this->getMockBuilder(\Magento\Customer\Model\Address::class)->disableOriginalConstructor() + ->setMethods(['setShouldIgnoreValidation'])->getMock(); + + /** @var \Magento\Customer\Api\Data\AddressInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->any()) + ->method('getId') + ->willReturn($addressId); + + /** @var Customer|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer->expects($this->any())->method('getId')->willReturn($customerId); + $customer->expects($this->any()) + ->method('getAddresses') + ->willReturn([$address]); + $this->addressRegistryMock->expects($this->once()) + ->method('retrieve') + ->with($addressId) + ->willReturn($addressModel); + $addressModel->expects($this->once()) + ->method('setShouldIgnoreValidation') + ->with(true); + $this->customerRepository->expects($this->atLeastOnce())->method('get')->with($customerEmail) + ->willReturn($customer); + $this->customer->expects($this->atLeastOnce())->method('getResetPasswordLinkExpirationPeriod') + ->willReturn(100000); + $this->string->expects($this->any())->method('strlen')->willReturnCallback( + function ($string) { + return strlen($string); + } + ); + $this->customerRegistry->expects($this->atLeastOnce())->method('retrieveSecureData') + ->willReturn($this->customerSecure); + + $this->customerSecure->expects($this->once())->method('setRpToken')->with(null); + $this->customerSecure->expects($this->once())->method('setRpTokenCreatedAt')->with(null); + $this->customerSecure->expects($this->any())->method('setPasswordHash')->willReturn(null); + + $this->sessionManager->expects($this->atLeastOnce())->method('destroy'); + $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); + $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) + ->disableOriginalConstructor() + ->setMethods(['getSessionId']) + ->getMock(); + $visitor->expects($this->atLeastOnce())->method('getSessionId') + ->willReturnOnConsecutiveCalls('session_id_1', 'session_id_2'); + $visitorCollection = $this->getMockBuilder( + \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class + ) + ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); + $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); + $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); + $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($visitorCollection); + $this->saveHandler->expects($this->atLeastOnce())->method('destroy') + ->withConsecutive( + ['session_id_1'], + ['session_id_2'] + ); + $this->assertTrue($this->accountManagement->resetPassword($customerEmail, $resetToken, $newPassword)); + } + + /** + * @return void + */ + public function testChangePasswordException() + { + $email = 'test@example.com'; + $currentPassword = '1234567'; + $newPassword = 'abcdefg'; + + $exception = new NoSuchEntityException( + new \Magento\Framework\Phrase('Exception message') + ); + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($email) + ->willThrowException($exception); + + $this->expectException(\Magento\Framework\Exception\InvalidEmailOrPasswordException::class); + $this->expectExceptionMessage('Invalid login or password.'); + + $this->accountManagement->changePassword($email, $currentPassword, $newPassword); + } + + /** + * @return void + */ + public function testAuthenticate() + { + $username = 'login'; + $password = '1234567'; + $passwordHash = '1a2b3f4c'; + + $customerData = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + + $customerModel = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customerModel->expects($this->once()) + ->method('updateData') + ->willReturn($customerModel); + + $this->customerRepository + ->expects($this->once()) + ->method('get') + ->with($username) + ->willReturn($customerData); + + $this->authenticationMock->expects($this->once()) + ->method('authenticate'); + + $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->setMethods(['getPasswordHash']) + ->disableOriginalConstructor() + ->getMock(); + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn($passwordHash); + + $this->customerRegistry->expects($this->any()) + ->method('retrieveSecureData') + ->willReturn($customerSecure); + + $this->customerFactory->expects($this->once()) + ->method('create') + ->willReturn($customerModel); + + $this->manager->expects($this->exactly(2)) + ->method('dispatch') + ->withConsecutive( + [ + 'customer_customer_authenticated', + ['model' => $customerModel, 'password' => $password], + ], + [ + 'customer_data_object_login', ['customer' => $customerData], + ] + ); + + $this->assertEquals($customerData, $this->accountManagement->authenticate($username, $password)); + } + + /** + * @param int $isConfirmationRequired + * @param string|null $confirmation + * @param string $expected + * @dataProvider dataProviderGetConfirmationStatus + */ + public function testGetConfirmationStatus( + $isConfirmationRequired, + $confirmation, + $expected + ) { + $websiteId = 1; + $customerId = 1; + $customerEmail = 'test1@example.com'; + + $customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customerMock->expects($this->once()) + ->method('getId') + ->willReturn($customerId); + $customerMock->expects($this->any()) + ->method('getConfirmation') + ->willReturn($confirmation); + $customerMock->expects($this->once()) + ->method('getEmail') + ->willReturn($customerEmail); + $customerMock->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($websiteId); + + $this->accountConfirmation->expects($this->once()) + ->method('isConfirmationRequired') + ->with($websiteId, $customerId, $customerEmail) + ->willReturn($isConfirmationRequired); + + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customerMock); + + $this->assertEquals($expected, $this->accountManagement->getConfirmationStatus($customerId)); + } + + /** + * @return array + */ + public function dataProviderGetConfirmationStatus() + { + return [ + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [1, null, AccountManagement::ACCOUNT_CONFIRMED], + [1, 'test', AccountManagement::ACCOUNT_CONFIRMATION_REQUIRED], + ]; + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithPasswordHashForGuest() + { + $storeId = 1; + $storeName = 'store_name'; + $websiteId = 1; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); + $storeMock->expects($this->once()) + ->method('getId') + ->willReturn($storeId); + $storeMock->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $storeMock->expects($this->once()) + ->method('getName') + ->willReturn($storeName); + + $this->storeManager->expects($this->exactly(3)) + ->method('getStore') + ->willReturn($storeMock); + + $customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customerMock->expects($this->exactly(2)) + ->method('getId') + ->willReturn(null); + $customerMock->expects($this->exactly(3)) + ->method('getStoreId') + ->willReturn(null); + $customerMock->expects($this->exactly(3)) + ->method('getWebsiteId') + ->willReturn(null); + $customerMock->expects($this->once()) + ->method('setStoreId') + ->with($storeId) + ->willReturnSelf(); + $customerMock->expects($this->once()) + ->method('setWebsiteId') + ->with($websiteId) + ->willReturnSelf(); + $customerMock->expects($this->once()) + ->method('setCreatedIn') + ->with($storeName) + ->willReturnSelf(); + $customerMock->expects($this->once()) + ->method('getAddresses') + ->willReturn(null); + $customerMock->expects($this->once()) + ->method('setAddresses') + ->with(null) + ->willReturnSelf(); + + $this->customerRepository + ->expects($this->once()) + ->method('save') + ->with($customerMock, $hash) + ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('Exception message'))); + + $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateAccountWithPasswordHashWithCustomerAddresses() + { + $websiteId = 1; + $addressId = 2; + $customerId = null; + $storeId = 1; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + + $this->prepareDateTimeFactory(); + + //Handle store + $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); + $store->expects($this->any()) + ->method('getWebsiteId') + ->willReturn($websiteId); + //Handle address - existing and non-existing. Non-Existing should return null when call getId method + $existingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $nonExistingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) + ->disableOriginalConstructor() + ->getMock(); + //Ensure that existing address is not in use + $this->addressRepository + ->expects($this->atLeastOnce()) + ->method("save") + ->withConsecutive( + [$this->logicalNot($this->identicalTo($existingAddress))], + [$this->identicalTo($nonExistingAddress)] + ); + + $existingAddress + ->expects($this->any()) + ->method("getId") + ->willReturn($addressId); + //Expects that id for existing address should be unset + $existingAddress + ->expects($this->once()) + ->method("setId") + ->with(null); + //Handle Customer calls + $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); + $customer + ->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer + ->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer + ->expects($this->any()) + ->method("getId") + ->willReturn($customerId); + //Return Customer from customer repository + $this->customerRepository + ->expects($this->atLeastOnce()) + ->method('save') + ->willReturn($customer); + $this->customerRepository + ->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) + ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) + ->disableOriginalConstructor() + ->getMock(); + $customerSecure->expects($this->once()) + ->method('setRpToken') + ->with($hash); + + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn($hash); + + $this->customerRegistry->expects($this->any()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($customerSecure); + + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($hash); + + $customer + ->expects($this->atLeastOnce()) + ->method('getAddresses') + ->willReturn([$existingAddress, $nonExistingAddress]); + + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getStore') + ->willReturn($store); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + + $this->assertSame($customer, $this->accountManagement->createAccountWithPasswordHash($customer, $hash)); + } + + /** + * @return string + */ + private function prepareDateTimeFactory() + { + $dateTime = '2017-10-25 18:57:08'; + $timestamp = '1508983028'; + $dateTimeMock = $this->createMock(\DateTime::class); + $dateTimeMock->expects($this->any()) + ->method('format') + ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) + ->willReturn($dateTime); + + $dateTimeMock + ->expects($this->any()) + ->method('getTimestamp') + ->willReturn($timestamp); + + $this->dateTimeFactory + ->expects($this->any()) + ->method('create') + ->willReturn($dateTimeMock); + + return $dateTime; + } + + /** + * @return void + */ + public function testCreateAccountUnexpectedValueException(): void + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; + $exception = new \UnexpectedValueException('Template file was not found'); + + $datetime = $this->prepareDateTimeFactory(); + + $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + $store = $this->createMock(\Magento\Store\Model\Store::class); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->createMock(\Magento\Store\Model\Website::class); + $website->expects($this->atLeastOnce()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->createMock(Customer::class); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->atLeastOnce()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->customerRepository->expects($this->atLeastOnce()) + ->method('save') + ->willReturn($customer); + $this->addressRepository->expects($this->atLeastOnce()) + ->method('save') + ->with($address); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($newLinkToken); + $customerSecure = $this->createPartialMock( + \Magento\Customer\Model\Data\CustomerSecure::class, + ['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash'] + ); + $customerSecure->expects($this->any()) + ->method('setRpToken') + ->with($newLinkToken); + $customerSecure->expects($this->any()) + ->method('setRpTokenCreatedAt') + ->with($datetime) + ->willReturnSelf(); + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn(null); + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($customerSecure); + $this->emailNotificationMock->expects($this->once()) + ->method('newAccount') + ->willThrowException($exception); + $this->logger->expects($this->once())->method('error')->with($exception); + + $this->accountManagement->createAccount($customer); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithStoreNotInWebsite() + { + $storeId = 1; + $websiteId = 1; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + $customerMock = $this->getMockBuilder(Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $customerMock->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(null); + $customerMock->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customerMock->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/DataProviderTest.php new file mode 100644 index 0000000000000..4dafd305d619d --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/Address/DataProviderTest.php @@ -0,0 +1,351 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Model\Address; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Address\DataProvider; +use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory; +use Magento\Customer\Model\ResourceModel\Address\Collection as AddressCollection; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Type; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Customer\Model\Address as AddressModel; +use Magento\Ui\Component\Form\Element\Multiline; +use Magento\Ui\Component\Form\Field; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DataProviderTest extends \PHPUnit\Framework\TestCase +{ + private const ATTRIBUTE_CODE = 'street'; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressCollectionFactory; + + /** + * @var AddressCollection|\PHPUnit_Framework_MockObject_MockObject + */ + private $collection; + + /** + * @var CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerRepository; + + /** + * @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $customer; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfig; + + /* + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var AddressModel|\PHPUnit_Framework_MockObject_MockObject + */ + private $address; + + /** + * @var FileUploaderDataResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileUploaderDataResolver; + + /** + * @var AttributeMetadataResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeMetadataResolver; + + /** + * @var DataProvider + */ + private $model; + + protected function setUp() + { + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeMetadataResolver = $this->getMockBuilder(AttributeMetadataResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->addressCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->collection = $this->getMockBuilder(AddressCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); + $this->context = $this->getMockForAbstractClass(ContextInterface::class); + $this->addressCollectionFactory->expects($this->once()) + ->method('create') + ->willReturn($this->collection); + $this->eavConfig = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->eavConfig->expects($this->once()) + ->method('getEntityType') + ->with('customer_address') + ->willReturn($this->getTypeAddressMock([])); + $this->customer = $this->getMockForAbstractClass(CustomerInterface::class); + $this->address = $this->getMockBuilder(AddressModel::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeMetadataResolver->expects($this->at(0)) + ->method('getAttributesMeta') + ->willReturn( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => Multiline::NAME, + 'formElement' => 'frontend_input', + 'options' => 'test-options', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('Street'), + 'sortOrder' => 'sort_order', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + ], + ], + ], + ] + ); + $this->attributeMetadataResolver->expects($this->at(1)) + ->method('getAttributesMeta') + ->willReturn( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + 'prefer' => 'toggle', + 'valueMap' => [ + 'true' => 1, + 'false' => 0, + ], + ], + ], + ], + ] + ); + $this->model = $objectManagerHelper->getObject( + DataProvider::class, + [ + 'name' => 'test-name', + 'primaryFieldName' => 'primary-field-name', + 'requestFieldName' => 'request-field-name', + 'addressCollectionFactory' => $this->addressCollectionFactory, + 'customerRepository' => $this->customerRepository, + 'eavConfig' => $this->eavConfig, + 'context' => $this->context, + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, + 'attributeMetadataResolver' => $this->attributeMetadataResolver, + [], + [], + true + ] + ); + } + + public function testGetDefaultData(): void + { + $expectedData = [ + '' => [ + 'parent_id' => 1, + 'firstname' => 'John', + 'lastname' => 'Doe' + ] + ]; + + $this->collection->expects($this->once()) + ->method('getItems') + ->willReturn([]); + + $this->context->expects($this->once()) + ->method('getRequestParam') + ->willReturn(1); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->willReturn($this->customer); + $this->customer->expects($this->once()) + ->method('getFirstname') + ->willReturn('John'); + $this->customer->expects($this->once()) + ->method('getLastname') + ->willReturn('Doe'); + + $this->assertEquals($expectedData, $this->model->getData()); + } + + public function testGetData(): void + { + $expectedData = [ + '1' => [ + 'parent_id' => '1', + 'default_billing' => '1', + 'default_shipping' => '1', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'street' => [ + '42000 Ave W 55 Cedar City', + 'Apt. 33' + ] + ] + ]; + + $this->collection->expects($this->once()) + ->method('getItems') + ->willReturn([ + $this->address + ]); + + $this->customerRepository->expects($this->once()) + ->method('getById') + ->willReturn($this->customer); + $this->customer->expects($this->once()) + ->method('getDefaultBilling') + ->willReturn('1'); + $this->customer->expects($this->once()) + ->method('getDefaultShipping') + ->willReturn('1'); + + $this->address->expects($this->once()) + ->method('getEntityId') + ->willReturn('1'); + $this->address->expects($this->once()) + ->method('load') + ->with('1') + ->willReturnSelf(); + $this->address->expects($this->once()) + ->method('getData') + ->willReturn([ + 'parent_id' => '1', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'street' => "42000 Ave W 55 Cedar City\nApt. 33" + ]); + $this->fileUploaderDataResolver->expects($this->once()) + ->method('overrideFileUploaderData') + ->willReturnSelf(); + + $this->assertEquals($expectedData, $this->model->getData()); + } + + /** + * Get customer address type mock + * + * @param array $customerAttributes + * @return Type|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getTypeAddressMock($customerAttributes = []) + { + $typeAddressMock = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->getMock(); + $attributesCollection = !empty($customerAttributes) ? $customerAttributes : $this->getAttributeMock(); + foreach ($attributesCollection as $attribute) { + $attribute->expects($this->any()) + ->method('getEntityType') + ->willReturn($typeAddressMock); + } + + $typeAddressMock->expects($this->once()) + ->method('getAttributeCollection') + ->willReturn($attributesCollection); + + return $typeAddressMock; + } + + /** + * Get attribute mock + * + * @param array $options + * @return AbstractAttribute[]|\PHPUnit_Framework_MockObject_MockObject[] + */ + protected function getAttributeMock($options = []): array + { + $attributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getAttributeCode', + 'getDataUsingMethod', + 'getFrontendInput', + 'getIsVisible', + 'getSource', + 'getIsUserDefined', + 'getUsedInForms', + 'getEntityType', + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $attributeCode = self::ATTRIBUTE_CODE; + if (isset($options[self::ATTRIBUTE_CODE]['specific_code_prefix'])) { + $attributeCode .= $options[self::ATTRIBUTE_CODE]['specific_code_prefix']; + } + + $attributeMock->expects($this->exactly(2)) + ->method('getAttributeCode') + ->willReturn($attributeCode); + + $attributeBooleanMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getAttributeCode', + 'getDataUsingMethod', + 'getFrontendInput', + 'getIsVisible', + 'getIsUserDefined', + 'getUsedInForms', + 'getSource', + 'getEntityType', + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $booleanAttributeCode = 'test-code-boolean'; + if (isset($options['test-code-boolean']['specific_code_prefix'])) { + $booleanAttributeCode .= $options['test-code-boolean']['specific_code_prefix']; + } + + $attributeBooleanMock->expects($this->exactly(2)) + ->method('getAttributeCode') + ->willReturn($booleanAttributeCode); + + $mocks = [$attributeMock, $attributeBooleanMock]; + return $mocks; + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php index d13c173cb12c2..ac87e2e336e3d 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php @@ -49,14 +49,14 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase protected $sessionMock; /** - * @var \Magento\Customer\Model\FileProcessorFactory|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Model\FileProcessor|\PHPUnit_Framework_MockObject_MockObject */ - protected $fileProcessorFactory; + protected $fileProcessor; /** - * @var \Magento\Customer\Model\FileProcessor|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Model\FileUploaderDataResolver|\PHPUnit_Framework_MockObject_MockObject */ - protected $fileProcessor; + private $fileUploaderDataResolver; /** * Set up @@ -84,10 +84,9 @@ protected function setUp() $this->fileProcessor = $this->getMockBuilder(\Magento\Customer\Model\FileProcessor::class) ->disableOriginalConstructor() ->getMock(); - - $this->fileProcessorFactory = $this->getMockBuilder(\Magento\Customer\Model\FileProcessorFactory::class) + $this->fileUploaderDataResolver = $this->getMockBuilder(\Magento\Customer\Model\FileUploaderDataResolver::class) ->disableOriginalConstructor() - ->setMethods(['create']) + ->setMethods(['overrideFileUploaderMetadata', 'overrideFileUploaderData']) ->getMock(); } @@ -111,16 +110,11 @@ public function testGetAttributesMetaWithOptions(array $expected) 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), - 'eavConfig' => $this->getEavConfigMock() + 'eavConfig' => $this->getEavConfigMock(), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver ] ); - $helper->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - $meta = $dataProvider->getMeta(); $this->assertNotEmpty($meta); $this->assertEquals($expected, $meta); @@ -591,10 +585,6 @@ public function testGetData() $customer->expects($this->once()) ->method('getAddresses') ->willReturn([$address]); - $customer->expects($this->once()) - ->method('getAttributes') - ->willReturn([]); - $address->expects($this->atLeastOnce()) ->method('getId') ->willReturn(2); @@ -605,9 +595,6 @@ public function testGetData() $address->expects($this->once()) ->method('getData') ->willReturn($addressData); - $address->expects($this->once()) - ->method('getAttributes') - ->willReturn([]); $helper = new ObjectManager($this); $dataProvider = $helper->getObject( @@ -618,7 +605,8 @@ public function testGetData() 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->getEavConfigMock() + 'eavConfig' => $this->getEavConfigMock(), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver ] ); @@ -631,12 +619,6 @@ public function testGetData() ->method('getCustomerFormData') ->willReturn(null); - $helper->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - $this->assertEquals( [ '' => [ @@ -723,10 +705,6 @@ public function testGetDataWithCustomerFormData() $customer->expects($this->once()) ->method('getAddresses') ->willReturn([$address]); - $customer->expects($this->once()) - ->method('getAttributes') - ->willReturn([]); - $address->expects($this->atLeastOnce()) ->method('getId') ->willReturn(2); @@ -741,10 +719,6 @@ public function testGetDataWithCustomerFormData() 'lastname' => 'lastname', 'street' => "street\nstreet", ]); - $address->expects($this->once()) - ->method('getAttributes') - ->willReturn([]); - $helper = new ObjectManager($this); $dataProvider = $helper->getObject( \Magento\Customer\Model\Customer\DataProvider::class, @@ -754,7 +728,8 @@ public function testGetDataWithCustomerFormData() 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->getEavConfigMock() + 'eavConfig' => $this->getEavConfigMock(), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver ] ); @@ -769,12 +744,6 @@ public function testGetDataWithCustomerFormData() $this->sessionMock->expects($this->once()) ->method('unsCustomerFormData'); - $helper->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - $this->assertEquals([$customerId => $customerFormData], $dataProvider->getData()); } @@ -788,42 +757,6 @@ public function testGetDataWithCustomAttributeImage() $customerEmail = 'user1@example.com'; $filename = '/filename.ext1'; - $viewUrl = 'viewUrl'; - $mime = 'image/png'; - - $expectedData = [ - $customerId => [ - 'customer' => [ - 'email' => $customerEmail, - 'img1' => [ - [ - 'file' => $filename, - 'size' => 1, - 'url' => $viewUrl, - 'name' => 'filename.ext1', - 'type' => $mime, - ], - ], - ], - ], - ]; - - $attributeMock = $this->getMockBuilder(\Magento\Customer\Model\Attribute::class) - ->disableOriginalConstructor() - ->getMock(); - $attributeMock->expects($this->exactly(2)) - ->method('getFrontendInput') - ->willReturn('image'); - $attributeMock->expects($this->exactly(2)) - ->method('getAttributeCode') - ->willReturn('img1'); - - $entityTypeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) - ->disableOriginalConstructor() - ->getMock(); - $entityTypeMock->expects($this->once()) - ->method('getEntityTypeCode') - ->willReturn(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER); $customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) ->disableOriginalConstructor() @@ -840,13 +773,6 @@ public function testGetDataWithCustomAttributeImage() $customerMock->expects($this->once()) ->method('getId') ->willReturn($customerId); - $customerMock->expects($this->once()) - ->method('getAttributes') - ->willReturn([$attributeMock]); - $customerMock->expects($this->once()) - ->method('getEntityType') - ->willReturn($entityTypeMock); - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) ->disableOriginalConstructor() ->getMock(); @@ -862,30 +788,6 @@ public function testGetDataWithCustomAttributeImage() ->method('getCustomerFormData') ->willReturn([]); - $this->fileProcessorFactory->expects($this->any()) - ->method('create') - ->with([ - 'entityTypeCode' => CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, - ]) - ->willReturn($this->fileProcessor); - - $this->fileProcessor->expects($this->once()) - ->method('isExist') - ->with($filename) - ->willReturn(true); - $this->fileProcessor->expects($this->once()) - ->method('getStat') - ->with($filename) - ->willReturn(['size' => 1]); - $this->fileProcessor->expects($this->once()) - ->method('getViewUrl') - ->with('/filename.ext1', 'image') - ->willReturn($viewUrl); - $this->fileProcessor->expects($this->once()) - ->method('getMimeType') - ->with($filename) - ->willReturn($mime); - $objectManager = new ObjectManager($this); $dataProvider = $objectManager->getObject( \Magento\Customer\Model\Customer\DataProvider::class, @@ -895,7 +797,8 @@ public function testGetDataWithCustomAttributeImage() 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->getEavConfigMock() + 'eavConfig' => $this->getEavConfigMock(), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver ] ); @@ -905,108 +808,15 @@ public function testGetDataWithCustomAttributeImage() $this->sessionMock ); - $objectManager->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - - $this->assertEquals($expectedData, $dataProvider->getData()); - } - - public function testGetDataWithCustomAttributeImageNoData() - { - $customerId = 1; - $customerEmail = 'user1@example.com'; - - $expectedData = [ - $customerId => [ - 'customer' => [ + $this->fileUploaderDataResolver->expects($this->atLeastOnce())->method('overrideFileUploaderData') + ->with( + $customerMock, + [ 'email' => $customerEmail, - 'img1' => [], - ], - ], - ]; - - $attributeMock = $this->getMockBuilder(\Magento\Customer\Model\Attribute::class) - ->disableOriginalConstructor() - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getFrontendInput') - ->willReturn('image'); - $attributeMock->expects($this->exactly(2)) - ->method('getAttributeCode') - ->willReturn('img1'); - - $entityTypeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) - ->disableOriginalConstructor() - ->getMock(); - $entityTypeMock->expects($this->once()) - ->method('getEntityTypeCode') - ->willReturn(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER); - - $customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) - ->disableOriginalConstructor() - ->getMock(); - $customerMock->expects($this->once()) - ->method('getData') - ->willReturn([ - 'email' => $customerEmail, - ]); - $customerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn([]); - $customerMock->expects($this->once()) - ->method('getId') - ->willReturn($customerId); - $customerMock->expects($this->once()) - ->method('getAttributes') - ->willReturn([$attributeMock]); - $customerMock->expects($this->once()) - ->method('getEntityType') - ->willReturn($entityTypeMock); - - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $collectionMock->expects($this->once()) - ->method('getItems') - ->willReturn([$customerMock]); - - $this->customerCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($collectionMock); - - $this->sessionMock->expects($this->once()) - ->method('getCustomerFormData') - ->willReturn([]); - - $objectManager = new ObjectManager($this); - $dataProvider = $objectManager->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, - [ - 'name' => 'test-name', - 'primaryFieldName' => 'primary-field-name', - 'requestFieldName' => 'request-field-name', - 'eavValidationRules' => $this->eavValidationRulesMock, - 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->getEavConfigMock() - ] - ); - - $objectManager->setBackwardCompatibleProperty( - $dataProvider, - 'session', - $this->sessionMock - ); - - $objectManager->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - - $this->assertEquals($expectedData, $dataProvider->getData()); + 'img1' => $filename, + ] + ); + $dataProvider->getData(); } /** @@ -1097,13 +907,6 @@ function ($origName) { 'file_extensions' => 'ext1, eXt2 ', // Added spaces and upper-cases ]); - $this->fileProcessorFactory->expects($this->any()) - ->method('create') - ->with([ - 'entityTypeCode' => CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, - ]) - ->willReturn($this->fileProcessor); - $objectManager = new ObjectManager($this); $dataProvider = $objectManager->getObject( \Magento\Customer\Model\Customer\DataProvider::class, @@ -1113,8 +916,7 @@ function ($origName) { 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->eavConfigMock, - 'fileProcessorFactory' => $this->fileProcessorFactory, + 'eavConfig' => $this->eavConfigMock ] ); @@ -1209,16 +1011,11 @@ public function testGetDataWithVisibleAttributes() 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), - 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)) + 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver ] ); - $helper->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); - $meta = $dataProvider->getMeta(); $this->assertNotEmpty($meta); $this->assertEquals($this->getExpectationForVisibleAttributes(), $meta); @@ -1282,14 +1079,11 @@ public function testGetDataWithVisibleAttributesWithAccountEdit() 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), 'context' => $context, - 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)) + 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)), + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + ] ); - $helper->setBackwardCompatibleProperty( - $dataProvider, - 'fileProcessorFactory', - $this->fileProcessorFactory - ); $meta = $dataProvider->getMeta(); $this->assertNotEmpty($meta); diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php new file mode 100644 index 0000000000000..2fc3cdb927723 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php @@ -0,0 +1,422 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model\Customer; + +use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Customer\Model\Customer\DataProviderWithDefaultAddresses; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory; +use Magento\Customer\Model\ResourceModel\Customer\Collection as CustomerCollection; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Eav\Model\Entity\Type; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Ui\Component\Form\Field; + +/** + * Test for class \Magento\Customer\Model\Customer\DataProviderWithDefaultAddresses + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DataProviderWithDefaultAddressesTest extends \PHPUnit\Framework\TestCase +{ + private const ATTRIBUTE_CODE = 'test-code'; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfigMock; + + /** + * @var CustomerCollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerCollectionFactoryMock; + + /** + * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionMock; + + /** + * @var \Magento\Directory\Model\CountryFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $countryFactoryMock; + + /** + * @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerMock; + + /** + * @var CustomerCollection|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerCollectionMock; + + /** + * @var FileUploaderDataResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileUploaderDataResolver; + + /** + * @var AttributeMetadataResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeMetadataResolver; + + /** + * @return void + */ + protected function setUp(): void + { + $this->eavConfigMock = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $this->customerCollectionFactoryMock = $this->createPartialMock(CustomerCollectionFactory::class, ['create']); + $this->sessionMock = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) + ->setMethods(['getCustomerFormData', 'unsCustomerFormData']) + ->getMockForAbstractClass(); + $this->countryFactoryMock = $this->getMockBuilder(\Magento\Directory\Model\CountryFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create', 'loadByCode', 'getName']) + ->getMock(); + $this->customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerCollectionMock = $this->getMockBuilder(CustomerCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerCollectionMock->expects($this->once())->method('addAttributeToSelect')->with('*'); + $this->customerCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->customerCollectionMock); + $this->eavConfigMock->expects($this->atLeastOnce()) + ->method('getEntityType') + ->with('customer') + ->willReturn($this->getTypeCustomerMock([])); + $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeMetadataResolver = $this->getMockBuilder(AttributeMetadataResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributesMeta']) + ->getMock(); + $this->attributeMetadataResolver->expects($this->at(0)) + ->method('getAttributesMeta') + ->willReturn( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'options' => 'test-options', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + ], + ], + ], + ] + ); + $this->attributeMetadataResolver->expects($this->at(1)) + ->method('getAttributesMeta') + ->willReturn( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + 'prefer' => 'toggle', + 'valueMap' => [ + 'true' => 1, + 'false' => 0, + ], + ], + ], + ], + ] + ); + $helper = new ObjectManager($this); + $this->dataProvider = $helper->getObject( + DataProviderWithDefaultAddresses::class, + [ + 'name' => 'test-name', + 'primaryFieldName' => 'primary-field-name', + 'requestFieldName' => 'request-field-name', + 'customerCollectionFactory' => $this->customerCollectionFactoryMock, + 'eavConfig' => $this->eavConfigMock, + 'countryFactory' => $this->countryFactoryMock, + 'session' => $this->sessionMock, + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, + 'attributeMetadataResolver' => $this->attributeMetadataResolver, + true + ] + ); + } + + /** + * Run test getAttributesMeta method + * + * @param array $expected + * @return void + * + * @dataProvider getAttributesMetaDataProvider + */ + public function testGetAttributesMetaWithOptions(array $expected): void + { + $meta = $this->dataProvider->getMeta(); + $this->assertNotEmpty($meta); + $this->assertEquals($expected, $meta); + } + + /** + * Data provider for testGetAttributesMeta + * + * @return array + */ + public function getAttributesMetaDataProvider(): array + { + return [ + [ + 'expected' => [ + 'customer' => [ + 'children' => [ + self::ATTRIBUTE_CODE => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'options' => 'test-options', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + ], + ], + ], + ], + 'test-code-boolean' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'visible' => null, + 'required' => 'is_required', + 'label' => __('frontend_label'), + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'componentType' => Field::NAME, + 'prefer' => 'toggle', + 'valueMap' => [ + 'true' => 1, + 'false' => 0, + ], + ], + ], + ], + ], + ], + ], + ] + ] + ]; + } + + /** + * @param array $customerAttributes + * @return Type|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getTypeCustomerMock($customerAttributes = []) + { + $typeCustomerMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + ->disableOriginalConstructor() + ->getMock(); + $attributesCollection = !empty($customerAttributes) ? $customerAttributes : $this->getAttributeMock(); + foreach ($attributesCollection as $attribute) { + $attribute->expects($this->any()) + ->method('getEntityType') + ->willReturn($typeCustomerMock); + } + + $typeCustomerMock->expects($this->atLeastOnce()) + ->method('getAttributeCollection') + ->willReturn($attributesCollection); + + return $typeCustomerMock; + } + + /** + * @param array $options + * @return AbstractAttribute[]|\PHPUnit_Framework_MockObject_MockObject[] + */ + protected function getAttributeMock($options = []): array + { + $attributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getAttributeCode', + 'getDataUsingMethod', + 'getFrontendInput', + 'getIsVisible', + 'getSource', + 'getIsUserDefined', + 'getUsedInForms', + 'getEntityType', + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $attributeCode = self::ATTRIBUTE_CODE; + if (isset($options[self::ATTRIBUTE_CODE]['specific_code_prefix'])) { + $attributeCode .= $options[self::ATTRIBUTE_CODE]['specific_code_prefix']; + } + + $attributeMock->expects($this->once()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + + $attributeBooleanMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getAttributeCode', + 'getDataUsingMethod', + 'getFrontendInput', + 'getIsVisible', + 'getIsUserDefined', + 'getUsedInForms', + 'getSource', + 'getEntityType', + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $booleanAttributeCode = 'test-code-boolean'; + if (isset($options['test-code-boolean']['specific_code_prefix'])) { + $booleanAttributeCode .= $options['test-code-boolean']['specific_code_prefix']; + } + + $attributeBooleanMock->expects($this->once()) + ->method('getAttributeCode') + ->willReturn($booleanAttributeCode); + + $mocks = [$attributeMock, $attributeBooleanMock]; + return $mocks; + } + + /** + * @return void + */ + public function testGetData(): void + { + $customerData = [ + 'email' => 'test@test.ua', + 'default_billing' => 2, + 'default_shipping' => 2, + 'password_hash' => 'password_hash', + 'rp_token' => 'rp_token', + 'confirmation' => 'confirmation', + ]; + + $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerCollectionMock->expects($this->once())->method('getItems')->willReturn([$this->customerMock]); + $this->customerMock->expects($this->once())->method('getData')->willReturn($customerData); + $this->customerMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); + + $this->customerMock->expects($this->once())->method('getDefaultBillingAddress')->willReturn($address); + $this->countryFactoryMock->expects($this->once())->method('create')->willReturnSelf(); + $this->countryFactoryMock->expects($this->once())->method('loadByCode')->willReturnSelf(); + $this->countryFactoryMock->expects($this->once())->method('getName')->willReturn('Ukraine'); + + $this->sessionMock->expects($this->once()) + ->method('getCustomerFormData') + ->willReturn(null); + + $this->assertEquals( + [ + 1 => [ + 'customer' => [ + 'email' => 'test@test.ua', + 'default_billing' => 2, + 'default_shipping' => 2, + ], + 'default_billing_address' => [ + 'country' => 'Ukraine', + ], + 'default_shipping_address' => [], + 'customer_id' => 1 + ] + ], + $this->dataProvider->getData() + ); + } + + /** + * @return void + */ + public function testGetDataWithCustomerFormData(): void + { + $customerId = 11; + $customerFormData = [ + 'customer' => [ + 'email' => 'test1@test1.ua', + 'default_billing' => 3, + 'default_shipping' => 3, + 'entity_id' => $customerId, + ], + 'address' => [ + 3 => [ + 'firstname' => 'firstname1', + 'lastname' => 'lastname1', + 'street' => [ + 'street1', + 'street2', + ], + 'default_billing' => 3, + 'default_shipping' => 3, + ], + ], + ]; + + $this->customerCollectionMock->expects($this->once())->method('getItems')->willReturn([$this->customerMock]); + $this->customerMock->expects($this->once()) + ->method('getData') + ->willReturn([ + 'email' => 'test@test.ua', + 'default_billing' => 2, + 'default_shipping' => 2, + ]); + $this->customerMock->expects($this->atLeastOnce())->method('getId')->willReturn($customerId); + + $this->sessionMock->expects($this->once())->method('getCustomerFormData')->willReturn($customerFormData); + $this->sessionMock->expects($this->once())->method('unsCustomerFormData'); + + $this->assertEquals([$customerId => $customerFormData], $this->dataProvider->getData()); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/RelationTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/RelationTest.php index e81637cfb23b2..319179c5e279a 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/RelationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/Address/RelationTest.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Test\Unit\Model\ResourceModel\Address; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Customer\Model\Address; /** * Class AddressTest @@ -40,7 +41,7 @@ protected function setUp() */ public function testProcessRelation($addressId, $isDefaultBilling, $isDefaultShipping) { - $addressModel = $this->createPartialMock(\Magento\Framework\Model\AbstractModel::class, [ + $addressModel = $this->createPartialMock(Address::class, [ '__wakeup', 'getId', 'getEntityTypeId', @@ -55,7 +56,17 @@ public function testProcessRelation($addressId, $isDefaultBilling, $isDefaultShi ]); $customerModel = $this->createPartialMock( \Magento\Customer\Model\Customer::class, - ['__wakeup', 'setDefaultBilling', 'setDefaultShipping', 'save', 'load', 'getResource', 'getId'] + [ + '__wakeup', + 'setDefaultBilling', + 'setDefaultShipping', + 'save', + 'load', + 'getResource', + 'getId', + 'getDefaultShippingAddress', + 'getDefaultBillingAddress' + ] ); $customerResource = $this->getMockForAbstractClass( \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, @@ -88,6 +99,7 @@ public function testProcessRelation($addressId, $isDefaultBilling, $isDefaultShi $this->customerFactoryMock->expects($this->any()) ->method('create') ->willReturn($customerModel); + if ($addressId && ($isDefaultBilling || $isDefaultShipping)) { $customerId = 1; $customerResource->expects($this->exactly(2))->method('getConnection')->willReturn($connectionMock); diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php index a142f87dcf6c4..05953b09b8c04 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php @@ -195,35 +195,6 @@ public function testSave() $customerId = 1; $storeId = 2; - $region = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\RegionInterface::class, [], '', false); - $address = $this->getMockForAbstractClass( - \Magento\Customer\Api\Data\AddressInterface::class, - [], - '', - false, - false, - true, - [ - 'setCustomerId', - 'setRegion', - 'getRegion', - 'getId' - ] - ); - $address2 = $this->getMockForAbstractClass( - \Magento\Customer\Api\Data\AddressInterface::class, - [], - '', - false, - false, - true, - [ - 'setCustomerId', - 'setRegion', - 'getRegion', - 'getId' - ] - ); $customerModel = $this->createPartialMock(\Magento\Customer\Model\Customer::class, [ 'getId', 'setId', @@ -243,10 +214,6 @@ public function testSave() $origCustomer = $this->customer; - $this->customer->expects($this->atLeastOnce()) - ->method('__toArray') - ->willReturn(['default_billing', 'default_shipping']); - $customerAttributesMetaData = $this->getMockForAbstractClass( \Magento\Framework\Api\CustomAttributesDataInterface::class, [], @@ -273,6 +240,9 @@ public function testSave() $this->customer->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); + $this->customer->expects($this->atLeastOnce()) + ->method('__toArray') + ->willReturn([]); $this->customerRegistry->expects($this->atLeastOnce()) ->method('retrieve') ->with($customerId) @@ -287,28 +257,6 @@ public function testSave() $this->customerRegistry->expects($this->atLeastOnce()) ->method("remove") ->with($customerId); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId) - ->willReturnSelf(); - $address->expects($this->once()) - ->method('getRegion') - ->willReturn($region); - $address->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn(7); - $address->expects($this->once()) - ->method('setRegion') - ->with($region); - $customerAttributesMetaData->expects($this->atLeastOnce()) - ->method('getAddresses') - ->willReturn([$address]); - $customerAttributesMetaData->expects($this->at(1)) - ->method('setAddresses') - ->with([]); - $customerAttributesMetaData->expects($this->at(2)) - ->method('setAddresses') - ->with([$address]); $this->extensibleDataObjectConverter->expects($this->once()) ->method('toNestedArray') ->with($customerAttributesMetaData, [], \Magento\Customer\Api\Data\CustomerInterface::class) @@ -393,12 +341,6 @@ public function testSave() $this->customerRegistry->expects($this->once()) ->method('push') ->with($customerModel); - $this->customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address, $address2]); - $this->addressRepository->expects($this->once()) - ->method('save') - ->with($address); $customerAttributesMetaData->expects($this->once()) ->method('getEmail') ->willReturn('example@example.com'); @@ -440,47 +382,8 @@ public function testSaveWithPasswordHash() 'getFirstFailure', 'getLockExpires', ]); - $region = $this->getMockForAbstractClass( - \Magento\Customer\Api\Data\RegionInterface::class, - [], - '', - false - ); - $address = $this->getMockForAbstractClass( - \Magento\Customer\Api\Data\AddressInterface::class, - [], - '', - false, - false, - true, - [ - 'setCustomerId', - 'setRegion', - 'getRegion', - 'getId' - ] - ); - $address2 = $this->getMockForAbstractClass( - \Magento\Customer\Api\Data\AddressInterface::class, - [], - '', - false, - false, - true, - [ - 'setCustomerId', - 'setRegion', - 'getRegion', - 'getId' - ] - ); - $origCustomer = $this->customer; - $this->customer->expects($this->atLeastOnce()) - ->method('__toArray') - ->willReturn(['default_billing', 'default_shipping']); - $customerModel = $this->createPartialMock(\Magento\Customer\Model\Customer::class, [ 'getId', 'setId', @@ -548,6 +451,9 @@ public function testSaveWithPasswordHash() $this->customer->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); + $this->customer->expects($this->atLeastOnce()) + ->method('__toArray') + ->willReturn([]); $this->customerRegistry->expects($this->atLeastOnce()) ->method('retrieve') ->with($customerId) @@ -559,28 +465,6 @@ public function testSaveWithPasswordHash() ->method('save') ->with($this->customer, CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, $this->customer) ->willReturn($customerAttributesMetaData); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId) - ->willReturnSelf(); - $address->expects($this->once()) - ->method('getRegion') - ->willReturn($region); - $address->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn(7); - $address->expects($this->once()) - ->method('setRegion') - ->with($region); - $customerAttributesMetaData->expects($this->any()) - ->method('getAddresses') - ->willReturn([$address]); - $customerAttributesMetaData->expects($this->at(1)) - ->method('setAddresses') - ->with([]); - $customerAttributesMetaData->expects($this->at(2)) - ->method('setAddresses') - ->with([$address]); $customerAttributesMetaData ->expects($this->atLeastOnce()) ->method('getId') @@ -618,12 +502,6 @@ public function testSaveWithPasswordHash() $this->customerRegistry->expects($this->once()) ->method('push') ->with($customerModel); - $this->customer->expects($this->any()) - ->method('getAddresses') - ->willReturn([$address, $address2]); - $this->addressRepository->expects($this->once()) - ->method('save') - ->with($address); $customerAttributesMetaData->expects($this->once()) ->method('getEmail') ->willReturn('example@example.com'); @@ -740,7 +618,7 @@ public function testGetList() ->willReturnSelf(); $collection->expects($this->at(7)) ->method('joinAttribute') - ->with('company', 'customer_address/company', 'default_billing', null, 'left') + ->with('billing_company', 'customer_address/company', 'default_billing', null, 'left') ->willReturnSelf(); $this->collectionProcessorMock->expects($this->once()) ->method('process') diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Form/AddressFieldsetTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Form/AddressFieldsetTest.php new file mode 100644 index 0000000000000..65a0443aed86f --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Form/AddressFieldsetTest.php @@ -0,0 +1,69 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Ui\Component\Form; + +use Magento\Customer\Ui\Component\Form\AddressFieldset; +use Magento\Framework\View\Element\UiComponent\ContextInterface; + +/** + * Test for class \Magento\Customer\Ui\Component\Form\AddressFieldset + */ +class AddressFieldsetTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var AddressFieldset + */ + protected $fieldset; + + /** + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $this->context = $this->getMockForAbstractClass( + \Magento\Framework\View\Element\UiComponent\ContextInterface::class + ); + $this->fieldset = new AddressFieldset( + $this->context, + [], + [] + ); + } + + /** + * Run test for canShow() method + * + * @return void + * + */ + public function testCanShow() + { + $this->context->expects($this->atLeastOnce())->method('getRequestParam')->with('id') + ->willReturn(1); + $this->assertTrue($this->fieldset->isComponentVisible()); + } + + /** + * Run test for canShow() method without customer id in context + * + * @return void + * + */ + public function testCanShowWithoutId() + { + $this->context->expects($this->atLeastOnce())->method('getRequestParam')->with('id') + ->willReturn(null); + $this->assertEquals(false, $this->fieldset->isComponentVisible()); + } +} diff --git a/app/code/Magento/Customer/Ui/Component/Form/AddressFieldset.php b/app/code/Magento/Customer/Ui/Component/Form/AddressFieldset.php new file mode 100644 index 0000000000000..3e3a6e74166a1 --- /dev/null +++ b/app/code/Magento/Customer/Ui/Component/Form/AddressFieldset.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Ui\Component\Form; + +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\ComponentVisibilityInterface; + +/** + * Customer addresses fieldset class + */ +class AddressFieldset extends \Magento\Ui\Component\Form\Fieldset implements ComponentVisibilityInterface +{ + /** + * @param ContextInterface $context + * @param array $components + * @param array $data + */ + public function __construct( + ContextInterface $context, + array $components = [], + array $data = [] + ) { + $this->context = $context; + + parent::__construct($context, $components, $data); + } + + /** + * Can show customer addresses tab in tabs or not + * + * Will return false for not registered customer in a case when admin user created new customer account. + * Needed to hide addresses tab from create new customer page + * + * @return boolean + */ + public function isComponentVisible(): bool + { + $customerId = $this->context->getRequestParam('id'); + return (bool)$customerId; + } +} diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Actions.php b/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Actions.php new file mode 100644 index 0000000000000..6d1d7472a798b --- /dev/null +++ b/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Actions.php @@ -0,0 +1,128 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Ui\Component\Listing\Address\Column; + +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\UrlInterface; + +/** + * Prepare actions column for customer addresses grid + */ +class Actions extends Column +{ + const CUSTOMER_ADDRESS_PATH_DELETE = 'customer/address/delete'; + const CUSTOMER_ADDRESS_PATH_DEFAULT_SHIPPING = 'customer/address/defaultShippingAddress'; + const CUSTOMER_ADDRESS_PATH_DEFAULT_BILLING = 'customer/address/defaultBillingAddress'; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @param ContextInterface $context + * @param UiComponentFactory $uiComponentFactory + * @param UrlInterface $urlBuilder + * @param array $components + * @param array $data + */ + public function __construct( + ContextInterface $context, + UiComponentFactory $uiComponentFactory, + UrlInterface $urlBuilder, + array $components = [], + array $data = [] + ) { + $this->urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource): array + { + if (isset($dataSource['data']['items'])) { + foreach ($dataSource['data']['items'] as &$item) { + $name = $this->getData('name'); + if (isset($item['entity_id'])) { + $item[$name]['edit'] = [ + 'callback' => [ + [ + 'provider' => 'customer_form.areas.address.address' + . '.customer_address_update_modal.update_customer_address_form_loader', + 'target' => 'destroyInserted', + ], + [ + 'provider' => 'customer_form.areas.address.address' + . '.customer_address_update_modal', + 'target' => 'openModal', + ], + [ + 'provider' => 'customer_form.areas.address.address' + . '.customer_address_update_modal.update_customer_address_form_loader', + 'target' => 'render', + 'params' => [ + 'entity_id' => $item['entity_id'], + ], + ] + ], + 'href' => '#', + 'label' => __('Edit'), + 'hidden' => false, + ]; + + $item[$name]['setDefaultBilling'] = [ + 'href' => $this->urlBuilder->getUrl( + self::CUSTOMER_ADDRESS_PATH_DEFAULT_BILLING, + ['parent_id' => $item['parent_id'], 'id' => $item['entity_id']] + ), + 'label' => __('Set as default billing'), + 'isAjax' => true, + 'confirm' => [ + 'title' => __('Set address as default billing'), + 'message' => __('Are you sure you want to set the address as default billing address?') + ] + ]; + + $item[$name]['setDefaultShipping'] = [ + 'href' => $this->urlBuilder->getUrl( + self::CUSTOMER_ADDRESS_PATH_DEFAULT_SHIPPING, + ['parent_id' => $item['parent_id'], 'id' => $item['entity_id']] + ), + 'label' => __('Set as default shipping'), + 'isAjax' => true, + 'confirm' => [ + 'title' => __('Set address as default shipping'), + 'message' => __('Are you sure you want to set the address as default shipping address?') + ] + ]; + + $item[$name]['delete'] = [ + 'href' => $this->urlBuilder->getUrl( + self::CUSTOMER_ADDRESS_PATH_DELETE, + ['parent_id' => $item['parent_id'], 'id' => $item['entity_id']] + ), + 'label' => __('Delete'), + 'isAjax' => true, + 'confirm' => [ + 'title' => __('Delete address'), + 'message' => __('Are you sure you want to delete the address?') + ] + ]; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Countries.php b/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Countries.php new file mode 100644 index 0000000000000..d05d5d1c592a7 --- /dev/null +++ b/app/code/Magento/Customer/Ui/Component/Listing/Address/Column/Countries.php @@ -0,0 +1,41 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Ui\Component\Listing\Address\Column; + +use Magento\Framework\Data\OptionSourceInterface; + +/** + * Class for process countries in customer addresses grid + */ +class Countries implements OptionSourceInterface +{ + /** + * @var \Magento\Directory\Model\ResourceModel\Country\CollectionFactory + */ + private $countryCollectionFactory; + + /** + * @param \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $collectionFactory + */ + public function __construct( + \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $collectionFactory + ) { + $this->countryCollectionFactory = $collectionFactory; + } + + /** + * Get list of countries with country id as value and code as label + * + * @return array + */ + public function toOptionArray(): array + { + /** @var \Magento\Directory\Model\ResourceModel\Country\Collection $countryCollection */ + $countryCollection = $this->countryCollectionFactory->create(); + return $countryCollection->toOptionArray(); + } +} diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php b/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php new file mode 100644 index 0000000000000..c70e25ee99ec3 --- /dev/null +++ b/app/code/Magento/Customer/Ui/Component/Listing/Address/DataProvider.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Ui\Component\Listing\Address; + +use Magento\Customer\Model\ResourceModel\Address\Grid\CollectionFactory; +use Magento\Directory\Model\CountryFactory; +use Magento\Framework\Api\Filter; + +/** + * Custom DataProvider for customer addresses listing + */ +class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * @var \Magento\Framework\App\RequestInterface $request, + */ + private $request; + + /** + * @var CountryFactory + */ + private $countryDirectory; + + /** + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param CollectionFactory $collectionFactory + * @param \Magento\Framework\App\RequestInterface $request + * @param CountryFactory $countryFactory + * @param array $meta + * @param array $data + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + CollectionFactory $collectionFactory, + \Magento\Framework\App\RequestInterface $request, + CountryFactory $countryFactory, + array $meta = [], + array $data = [] + ) { + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + $this->collection = $collectionFactory->create(); + $this->countryDirectory = $countryFactory->create(); + $this->request = $request; + } + + /** + * Add country key for default billing/shipping blocks on customer addresses tab + * + * @return array + */ + public function getData(): array + { + $collection = $this->getCollection(); + $data['items'] = []; + if ($this->request->getParam('parent_id')) { + $collection->addFieldToFilter('parent_id', $this->request->getParam('parent_id')); + $data = $collection->toArray(); + } + foreach ($data['items'] as $key => $item) { + if (isset($item['country_id']) && !isset($item['country'])) { + $data['items'][$key]['country'] = $this->countryDirectory->loadByCode($item['country_id'])->getName(); + } + } + + return $data; + } + + /** + * Add full text search filter to collection + * + * @param Filter $filter + * @return void + */ + public function addFilter(Filter $filter): void + { + if ($filter->getField() !== 'fulltext') { + $this->collection->addFieldToFilter( + $filter->getField(), + [$filter->getConditionType() => $filter->getValue()] + ); + } else { + $value = trim($filter->getValue()); + $this->collection->addFieldToFilter( + [ + ['attribute' => 'firstname'], + ['attribute' => 'lastname'], + ['attribute' => 'street'], + ['attribute' => 'city'], + ['attribute' => 'region'], + ['attribute' => 'postcode'], + ['attribute' => 'telephone'] + ], + [ + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ['like' => "%{$value}%"], + ] + ); + } + } +} diff --git a/app/code/Magento/Customer/etc/db_schema_whitelist.json b/app/code/Magento/Customer/etc/db_schema_whitelist.json index 4aada8f0d81fe..ec7a53945aba3 100644 --- a/app/code/Magento/Customer/etc/db_schema_whitelist.json +++ b/app/code/Magento/Customer/etc/db_schema_whitelist.json @@ -73,7 +73,8 @@ "vat_request_success": true }, "index": { - "CUSTOMER_ADDRESS_ENTITY_PARENT_ID": true + "CUSTOMER_ADDRESS_ENTITY_PARENT_ID": true, + "FTI_BA70344390184AC3F063AB2EB38BC0ED": true }, "constraint": { "PRIMARY": true, diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index 209f4794c6fcf..a181d6dd217fd 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -230,6 +230,7 @@ <item name="customer_listing_data_source" xsi:type="string">Magento\Customer\Model\ResourceModel\Grid\Collection</item> <item name="customer_online_grid_data_source" xsi:type="string">Magento\Customer\Model\ResourceModel\Online\Grid\Collection</item> <item name="customer_group_listing_data_source" xsi:type="string">Magento\Customer\Model\ResourceModel\Group\Grid\Collection</item> + <item name="customer_address_listing_data_source" xsi:type="string">Magento\Customer\Model\ResourceModel\Address\Grid\Collection</item> </argument> </arguments> </type> @@ -456,6 +457,14 @@ <argument name="resourceModel" xsi:type="string">Magento\Customer\Model\ResourceModel\Group</argument> </arguments> </type> + <type name="Magento\Customer\Model\ResourceModel\Address\Grid\Collection"> + <arguments> + <argument name="mainTable" xsi:type="string">customer_address_entity</argument> + <argument name="eventPrefix" xsi:type="string">customer_address_entity_grid_collection</argument> + <argument name="eventObject" xsi:type="string">customer_address_entity_grid_collection</argument> + <argument name="resourceModel" xsi:type="string">Magento\Customer\Model\ResourceModel\Address</argument> + </arguments> + </type> <preference for="Magento\Customer\Api\AccountDelegationInterface" type="Magento\Customer\Model\Delegation\AccountDelegation" /> diff --git a/app/code/Magento/Customer/view/adminhtml/layout/customer_address_edit.xml b/app/code/Magento/Customer/view/adminhtml/layout/customer_address_edit.xml new file mode 100644 index 0000000000000..3acae3acec8aa --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/layout/customer_address_edit.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <update handle="styles"/> + <body> + <referenceContainer name="content"> + <uiComponent name="customer_address_form"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml new file mode 100644 index 0000000000000..35f0ed8f1dae2 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml @@ -0,0 +1,250 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd" component="Magento_Customer/js/form/components/form"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">customer_address_form.customer_address_form_data_source</item> + </item> + <item name="config" xsi:type="array"> + <item name="deleteConfirmationMessage" translate="true" xsi:type="string">Are you sure you want to delete this address?</item> + </item> + <item name="label" xsi:type="string" translate="true">Update Address</item> + <item name="reverseMetadataMerge" xsi:type="boolean">true</item> + <item name="template" xsi:type="string">templates/form/collapsible</item> + </argument> + <settings> + <buttons> + <button name="cancel" class="Magento\Customer\Block\Adminhtml\Edit\Address\CancelButton"/> + <button name="delete" class="Magento\Customer\Block\Adminhtml\Edit\Address\DeleteButton"/> + <button name="save" class="Magento\Customer\Block\Adminhtml\Edit\Address\SaveButton"/> + </buttons> + <namespace>customer_address_form</namespace> + <ajaxSave>true</ajaxSave> + <ajaxSaveType>simple</ajaxSaveType> + <dataScope>data</dataScope> + <deps> + <dep>customer_address_form.customer_address_form_data_source</dep> + </deps> + </settings> + <dataSource name="customer_address_form_data_source"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item> + </item> + </argument> + <settings> + <submitUrl path="customer/address/save"/> + <validateUrl path="customer/address/validate"/> + </settings> + <aclResource>Magento_Customer::manage</aclResource> + <dataProvider class="Magento\Customer\Model\Address\DataProvider" name="customer_address_form_data_source"> + <settings> + <requestFieldName>entity_id</requestFieldName> + <primaryFieldName>entity_id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <container name="messages" component="Magento_Ui/js/form/components/html"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="additionalClasses" xsi:type="string">message message-error</item> + <item name="visible" xsi:type="boolean">false</item> + <item name="imports" xsi:type="array"> + <item name="responseData" xsi:type="string">${ $.parentName }:responseData</item> + </item> + <item name="listens" xsi:type="array"> + <item name="responseData.error" xsi:type="string">visible</item> + <item name="responseData.messages" xsi:type="string">content</item> + </item> + </item> + </argument> + </container> + <fieldset name="general"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="is_collection" xsi:type="boolean">true</item> + </item> + </argument> + <settings> + <label/> + <dataScope/> + </settings> + + <field name="entity_id" formElement="hidden"> + <settings> + <dataType>text</dataType> + </settings> + </field> + <field name="default_billing" sortOrder="5" formElement="checkbox"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="default" xsi:type="number">0</item> + </item> + </argument> + <settings> + <dataType>boolean</dataType> + <label translate="true">Default Billing Address</label> + <dataScope>default_billing</dataScope> + </settings> + <formElements> + <checkbox> + <settings> + <valueMap> + <map name="false" xsi:type="number">0</map> + <map name="true" xsi:type="number">1</map> + </valueMap> + <prefer>toggle</prefer> + </settings> + </checkbox> + </formElements> + </field> + <field name="default_shipping" sortOrder="7" formElement="checkbox"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="default" xsi:type="number">0</item> + </item> + </argument> + <settings> + <dataType>boolean</dataType> + <label translate="true">Default Shipping Address</label> + <dataScope>default_shipping</dataScope> + </settings> + <formElements> + <checkbox> + <settings> + <valueMap> + <map name="false" xsi:type="number">0</map> + <map name="true" xsi:type="number">1</map> + </valueMap> + <prefer>toggle</prefer> + </settings> + </checkbox> + </formElements> + </field> + <field name="prefix" sortOrder="10" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Name Prefix</label> + </settings> + </field> + <field name="firstname" sortOrder="20" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">First Name</label> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + </settings> + </field> + <field name="lastname" sortOrder="30" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Last Name</label> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + </settings> + </field> + <field name="suffix" sortOrder="40" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Name Suffix</label> + </settings> + </field> + <field name="middlename" sortOrder="50" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Middle Name/Initial</label> + </settings> + </field> + <field name="company" sortOrder="60" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Company</label> + </settings> + </field> + <field name="city" sortOrder="80" formElement="input"> + <settings> + <dataType>text</dataType> + <label translate="true">City</label> + <visible>true</visible> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + </settings> + </field> + <field name="country_id" component="Magento_Customer/js/form/element/country" sortOrder="90" formElement="select"> + <settings> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + <dataType>text</dataType> + </settings> + <formElements> + <select> + <settings> + <options class="Magento\Directory\Model\ResourceModel\Country\Collection"/> + </settings> + </select> + </formElements> + </field> + <field name="region_id" component="Magento_Customer/js/form/element/region" formElement="select"> + <settings> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + <dataType>text</dataType> + <label translate="true">State/Province</label> + </settings> + <formElements> + <select> + <settings> + <filterBy> + <field>country_id</field> + <target>${ $.provider }:${ $.parentScope }.country_id</target> + </filterBy> + <customEntry>region</customEntry> + <options class="Magento\Directory\Model\ResourceModel\Region\Collection"/> + </settings> + </select> + </formElements> + </field> + <field name="postcode" component="Magento_Ui/js/form/element/post-code" sortOrder="120" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Zip/Postal Code</label> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + </settings> + </field> + <field name="telephone" sortOrder="130" formElement="input"> + <settings> + <dataType>text</dataType> + <visible>true</visible> + <label translate="true">Phone Number</label> + </settings> + </field> + <field name="vat_id" sortOrder="140" formElement="input"> + <settings> + <dataType>text</dataType> + <label translate="true">VAT Number</label> + <validation> + <rule name="validate-alphanum" xsi:type="boolean">true</rule> + </validation> + </settings> + </field> + </fieldset> +</form> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_listing.xml new file mode 100644 index 0000000000000..fb42a2c5a0787 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_listing.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">customer_address_listing.customer_address_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>customer_address_columns</spinner> + <deps> + <dep>customer_address_listing.customer_address_listing_data_source</dep> + </deps> + </settings> + <dataSource name="customer_address_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <filterUrlParams> + <param name="id">*</param> + </filterUrlParams> + <storageConfig> + <param name="indexField" xsi:type="string">entity_id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Customer::manage</aclResource> + <dataProvider class="Magento\Customer\Ui\Component\Listing\Address\DataProvider" name="customer_address_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>entity_id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <bookmark name="bookmarks"/> + <columnsControls name="columns_controls"/> + <filterSearch name="fulltext"/> + <filters name="listing_filters" component="Magento_Customer/js/grid/filters/filters"> + <settings> + <storageConfig> + <param name="provider" xsi:type="string">customer_address_listing.customer_address_listing.listing_top.bookmarks</param> + <param name="namespace" xsi:type="string">current.filters</param> + </storageConfig> + <childDefaults> + <param name="provider" xsi:type="string">customer_address_listing.customer_address_listing.listing_top.listing_filters</param> + <param name="imports" xsi:type="array"> + <item name="visible" xsi:type="string">customer_address_listing.customer_address_listing.listing_top.bookmarks:current.columns.${ $.index }.visible</item> + </param> + </childDefaults> + </settings> + </filters> + <massaction name="listing_massaction" component="Magento_Customer/js/grid/massactions"> + <action name="delete"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="isAjax" xsi:type="boolean">true</item> + </item> + </argument> + <settings> + <url path="customer/address/massDelete"/> + <type>delete</type> + <label translate="true">Delete</label> + <confirm> + <message translate="true">Are you sure to delete selected address?</message> + <title translate="true">Delete items + + + + + + + + + + + + false + + entity_id + true + customer_address_listing.customer_address_listing.address_columns.ids + + + + customer_address_listing.customer_address_listing.address_columns_editor + startEdit + + ${ $.$data.rowIndex } + true + + + + + + + entity_id + + + + + text + + + + + + text + + + + + + text + + + + + + text + + + + + + text + + + + + + text + + + + + + select + + select + + + + + + text + + text + + + + + + + entity_id + + + + diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/address/default-address.js b/app/code/Magento/Customer/view/adminhtml/web/js/address/default-address.js new file mode 100644 index 0000000000000..07662cd9b105b --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/address/default-address.js @@ -0,0 +1,50 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/components/button', + 'underscore' +], function (Button, _) { + 'use strict'; + + return Button.extend({ + defaults: { + entityId: null, + parentId: null, + listens: { + entity: 'changeVisibility' + } + }, + + /** + * Apply action on target component, + * but previously create this component from template if it is not existed + * + * @param {Object} action - action configuration + */ + applyAction: function (action) { + if (action.params && action.params[0]) { + action.params[0]['entity_id'] = this.entityId; + action.params[0]['parent_id'] = this.parentId; + } else { + action.params = [{ + 'entity_id': this.entityId, + 'parent_id': this.parentId + }]; + } + + this._super(); + }, + + /** + * Change visibility of the default address shipping/billing blocks + * + * @param {Object} entity - customer address + */ + changeVisibility: function (entity) { + this.visible(!_.isEmpty(entity)); + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/components/form.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/form.js new file mode 100644 index 0000000000000..812dbe5975c70 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/form.js @@ -0,0 +1,81 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'Magento_Ui/js/modal/alert', + 'Magento_Ui/js/modal/confirm', + 'Magento_Ui/js/form/form', + 'underscore', + 'mage/translate' +], function ($, uiAlert, uiConfirm, Form, _, $t) { + 'use strict'; + + return Form.extend({ + defaults: { + deleteConfirmationMessage: '', + ajaxSettings: { + method: 'POST', + dataType: 'json' + } + }, + + /** + * Delete customer address by provided url. + * Will call confirmation message to be sure that user is really wants to delete this address + * + * @param {String} url - ajax url + */ + deleteAddress: function (url) { + var that = this; + + uiConfirm({ + content: this.deleteConfirmationMessage, + actions: { + /** @inheritdoc */ + confirm: function () { + that._delete(url); + } + } + }); + }, + + /** + * Perform asynchronous DELETE request to server. + * @param {String} url - ajax url + * @returns {Deferred} + */ + _delete: function (url) { + var settings = _.extend({}, this.ajaxSettings, { + url: url, + data: { + 'form_key': window.FORM_KEY + } + }), + that = this; + + $('body').trigger('processStart'); + + return $.ajax(settings) + .done(function (response) { + if (response.error) { + uiAlert({ + content: response.message + }); + } else { + that.trigger('deleteAddressAction', that.source.get('data.entity_id')); + } + }) + .fail(function () { + uiAlert({ + content: $t('Sorry, there has been an error processing your request. Please try again later.') + }); + }) + .always(function () { + $('body').trigger('processStop'); + }); + + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-form.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-form.js new file mode 100644 index 0000000000000..a1ffbde62d4d0 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-form.js @@ -0,0 +1,80 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/components/insert-form' +], function (Insert) { + 'use strict'; + + return Insert.extend({ + defaults: { + listens: { + responseData: 'onResponse' + }, + modules: { + addressListing: '${ $.addressListingProvider }', + addressModal: '${ $.addressModalProvider }' + } + }, + + /** + * Close modal, reload customer address listing and save customer address + * + * @param {Object} responseData + */ + onResponse: function (responseData) { + var data; + + if (!responseData.error) { + this.addressModal().closeModal(); + this.addressListing().reload({ + refresh: true + }); + data = this.externalSource().get('data'); + this.saveAddress(responseData, data); + } + }, + + /** + * Save customer address to customer form data source + * + * @param {Object} responseData + * @param {Object} data - customer address + */ + saveAddress: function (responseData, data) { + data['entity_id'] = responseData.data['entity_id']; + + if (parseFloat(data['default_billing'])) { + this.source.set('data.default_billing_address', data); + } else if ( + parseFloat(this.source.get('data.default_billing_address')['entity_id']) === data['entity_id'] + ) { + this.source.set('data.default_billing_address', []); + } + + if (parseFloat(data['default_shipping'])) { + this.source.set('data.default_shipping_address', data); + } else if ( + parseFloat(this.source.get('data.default_shipping_address')['entity_id']) === data['entity_id'] + ) { + this.source.set('data.default_shipping_address', []); + } + }, + + /** + * Event method that closes "Edit customer address" modal and refreshes grid after customer address + * was removed through "Delete" button on the "Edit customer address" modal + * + * @param {String} id - customer address ID to delete + */ + onAddressDelete: function (id) { + this.addressModal().closeModal(); + this.addressListing().reload({ + refresh: true + }); + this.addressListing()._delete([parseFloat(id)]); + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-listing.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-listing.js new file mode 100644 index 0000000000000..912d4b32130ec --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/components/insert-listing.js @@ -0,0 +1,90 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/components/insert-listing', + 'underscore' +], function (Insert, _) { + 'use strict'; + + return Insert.extend({ + + /** + * On action call + * + * @param {Object} data - customer address and actions + */ + onAction: function (data) { + this[data.action + 'Action'].call(this, data.data); + }, + + /** + * On mass action call + * + * @param {Object} data - customer address + */ + onMassAction: function (data) { + this[data.action + 'Massaction'].call(this, data.data); + }, + + /** + * Set default billing address + * + * @param {Object} data - customer address + */ + setDefaultBillingAction: function (data) { + this.source.set('data.default_billing_address', data); + }, + + /** + * Set default shipping address + * + * @param {Object} data - customer address + */ + setDefaultShippingAction: function (data) { + this.source.set('data.default_shipping_address', data); + }, + + /** + * Delete customer address + * + * @param {Object} data - customer address + */ + deleteAction: function (data) { + this._delete([parseFloat(data[data['id_field_name']])]); + }, + + /** + * Mass action delete + * + * @param {Object} data - customer address + */ + deleteMassaction: function (data) { + var ids = _.map(data, function (val) { + return parseFloat(val); + }); + + this._delete(ids); + }, + + /** + * Delete customer address by ids + * + * @param {Array} ids + */ + _delete: function (ids) { + var defaultShippingId = parseFloat(this.source.get('data.default_shipping_address.entity_id')), + defaultBillingId = parseFloat(this.source.get('data.default_billing_address.entity_id')); + + if (ids.indexOf(defaultShippingId) !== -1) { + this.source.set('data.default_shipping_address', []); + } + + if (ids.indexOf(defaultBillingId) !== -1) { + this.source.set('data.default_billing_address', []); + } + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/element/country.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/country.js new file mode 100644 index 0000000000000..1ed30ae388b47 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/country.js @@ -0,0 +1,29 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/country' +], function (Country) { + 'use strict'; + + return Country.extend({ + defaults: { + countryScope: 'data.country' + }, + + /** + * Set country to customer address form + * + * @param {String} value - country + */ + setDifferedFromDefault: function (value) { + this._super(); + + if (value) { + this.source.set(this.countryScope, this.indexedOptions[value].label); + } + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/element/region.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/region.js new file mode 100644 index 0000000000000..755a8e6df3dbe --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/region.js @@ -0,0 +1,29 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/region' +], function (Region) { + 'use strict'; + + return Region.extend({ + defaults: { + regionScope: 'data.region' + }, + + /** + * Set region to customer address form + * + * @param {String} value - region + */ + setDifferedFromDefault: function (value) { + this._super(); + + if (parseFloat(value)) { + this.source.set(this.regionScope, this.indexedOptions[value].label); + } + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js b/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js new file mode 100644 index 0000000000000..66ef89c9413c7 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/grid/columns/actions.js @@ -0,0 +1,103 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/grid/columns/actions', + 'Magento_Ui/js/modal/alert', + 'underscore', + 'jquery', + 'mage/translate' +], function (Actions, uiAlert, _, $, $t) { + 'use strict'; + + return Actions.extend({ + defaults: { + ajaxSettings: { + method: 'POST', + dataType: 'json' + }, + listens: { + action: 'onAction' + } + }, + + /** + * Reload customer address listing data source after customer address delete action + * + * @param {Object} data + */ + onAction: function (data) { + if (data.action === 'delete') { + this.source().reload({ + refresh: true + }); + } + }, + + /** + * Default action callback. Redirects to + * the specified in action's data url. + * + * @param {String} actionIndex - Action's identifier. + * @param {(Number|String)} recordId - Id of the record associated + * with a specified action. + * @param {Object} action - Action's data. + */ + defaultCallback: function (actionIndex, recordId, action) { + if (action.isAjax) { + this.request(action.href).done(function (response) { + var data; + + if (!response.error) { + data = _.findWhere(this.rows, { + _rowIndex: action.rowIndex + }); + + this.trigger('action', { + action: actionIndex, + data: data + }); + } + }.bind(this)); + + } else { + this._super(); + } + }, + + /** + * Send customer address listing ajax request + * + * @param {String} href + */ + request: function (href) { + var settings = _.extend({}, this.ajaxSettings, { + url: href, + data: { + 'form_key': window.FORM_KEY + } + }); + + $('body').trigger('processStart'); + + return $.ajax(settings) + .done(function (response) { + if (response.error) { + uiAlert({ + content: response.message + }); + } + }) + .fail(function () { + uiAlert({ + content: $t('Sorry, there has been an error processing your request. Please try again later.') + }); + }) + .always(function () { + $('body').trigger('processStop'); + }); + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/chips.js b/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/chips.js new file mode 100644 index 0000000000000..5dbf62a209b3b --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/chips.js @@ -0,0 +1,23 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/grid/filters/chips' +], function (Chips) { + 'use strict'; + + return Chips.extend({ + + /** + * Clear previous filters while initializing element to prevent filters sharing between customers + * + * @param {Object} elem + */ + initElement: function (elem) { + this.clear(); + this._super(elem); + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/filters.js b/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/filters.js new file mode 100644 index 0000000000000..f8bf7d3d7ec46 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/grid/filters/filters.js @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/grid/filters/filters' +], function (Filters) { + 'use strict'; + + return Filters.extend({ + defaults: { + chipsConfig: { + name: '${ $.name }_chips', + provider: '${ $.chipsConfig.name }', + component: 'Magento_Customer/js/grid/filters/chips' + } + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/grid/massactions.js b/app/code/Magento/Customer/view/adminhtml/web/js/grid/massactions.js new file mode 100644 index 0000000000000..384f48554a917 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/js/grid/massactions.js @@ -0,0 +1,106 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/grid/massactions', + 'Magento_Ui/js/modal/alert', + 'underscore', + 'jquery', + 'mage/translate' +], function (Massactions, uiAlert, _, $, $t) { + 'use strict'; + + return Massactions.extend({ + defaults: { + ajaxSettings: { + method: 'POST', + dataType: 'json' + }, + listens: { + massaction: 'onAction' + } + }, + + /** + * Reload customer addresses listing + * + * @param {Object} data + */ + onAction: function (data) { + if (data.action === 'delete') { + this.source.reload({ + refresh: true + }); + } + }, + + /** + * Default action callback. Send selections data + * via POST request. + * + * @param {Object} action - Action data. + * @param {Object} data - Selections data. + */ + defaultCallback: function (action, data) { + var itemsType, selections; + + if (action.isAjax) { + itemsType = data.excludeMode ? 'excluded' : 'selected'; + selections = {}; + + selections[itemsType] = data[itemsType]; + + if (!selections[itemsType].length) { + selections[itemsType] = false; + } + + _.extend(selections, data.params || {}); + + this.request(action.url, selections).done(function (response) { + if (!response.error) { + this.trigger('massaction', { + action: action.type, + data: selections + }); + } + }.bind(this)); + } else { + this._super(); + } + }, + + /** + * Send customer address listing mass action ajax request + * + * @param {String} href + * @param {Object} data + */ + request: function (href, data) { + var settings = _.extend({}, this.ajaxSettings, { + url: href, + data: data + }); + + $('body').trigger('processStart'); + + return $.ajax(settings) + .done(function (response) { + if (response.error) { + uiAlert({ + content: response.message + }); + } + }) + .fail(function () { + uiAlert({ + content: $t('Sorry, there has been an error processing your request. Please try again later.') + }); + }) + .always(function () { + $('body').trigger('processStop'); + }); + } + }); +}); diff --git a/app/code/Magento/Customer/view/adminhtml/web/template/default-address-wrapper.html b/app/code/Magento/Customer/view/adminhtml/web/template/default-address-wrapper.html new file mode 100644 index 0000000000000..2af366b03342c --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/template/default-address-wrapper.html @@ -0,0 +1,7 @@ + +
diff --git a/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html b/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html new file mode 100644 index 0000000000000..8d68b93445b85 --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html @@ -0,0 +1,47 @@ +
+
+
+
+ + + + +
+
+ + + + + +
+ + +
+
+ + + +
+
+ + + + +
+
+ + +
T: +
+ +
F: +
+ +
VAT: +
+
+ +
+
+
diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index d2a9b3f44624d..7e6b7bbe9cd09 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -43,7 +43,7 @@ - + id entity_id @@ -303,262 +303,196 @@ -
- - - true - Are you sure you want to delete this item? - - +
+ false + fieldset + + true + - - - - address - - - - number - false - - - - - - address - - - - text - true - - - - - - address - - - - - true - - text - - ${ $.provider }:data.customer.firstname - - - - - - - address - - - - text - true - - - - - - address + + + + + billing-address + Default Billing Address + customer-default-billing-address-content + The customer does not have default billing address + + true + + + + + + ${ $.provider}:data.default_billing_address + + + + + + + + + + shipping-address + Default Shipping Address + customer-default-shipping-address-content + The customer does not have default shipping address + + true + + + + + + ${ $.provider}:data.default_shipping_address + + + + + + + - - 0 - - text - - - - - - address - - - - - true - - text + + + - - - - - address - - - - - true - - text + + + + ns = customer_address_listing, index = customer_address_listing + ${ $.parentName } + + + + ajax + + customer_address_edit + 1 + + false + ${ $.parentName } + ${ $.ns }.customer_address_form_data_source + customer_address_form + + ${ $.externalProvider }:data.parent_id + + + ${ $.provider}:data.customer_id + ${ $.ns }.${ $.ns }:deleteAddressAction + + + + + + + + false + true + + customer_address_listing.customer_address_listing_data_source + customer_address_listing.customer_address_listing.customer_address_listing_columns.ids + true + customer_address_listing + customer_address_listing + + ${ $.externalProvider }:params.parent_id + - ${ $.provider }:data.customer.website_id + ${ $.provider }:data.customer.entity_id + ns = ${ $.ns }, index = actions:action + ns = ${ $.ns }, index = listing_massaction:massaction - - - - - address - - - - text - false - - - - - - address - - - - - true - - text - - - - - - - - - address - - - - - true - - text - - - - - - address - - - - text - - - - - - address - - - - - 0 - - text - - - - - - address - - - - text - - - - - - default_billing - value - - customer - default_billing - entity_id - - - - - ui/form/element/checkbox - boolean - - - - - Default Billing Address - - - - - - - - default_shipping - value - - customer - default_shipping - entity_id - - - - - ui/form/element/checkbox - boolean - - - - - Default Shipping Address - - - - +
diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml b/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml index bad120e46277f..2c5e5b98e5f7b 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml @@ -13,6 +13,7 @@ + diff --git a/app/code/Magento/Customer/view/frontend/templates/address/book.phtml b/app/code/Magento/Customer/view/frontend/templates/address/book.phtml index 45c13d9357425..9d09a090deac1 100644 --- a/app/code/Magento/Customer/view/frontend/templates/address/book.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/address/book.phtml @@ -62,47 +62,3 @@
- - - -
-
- -
- -
- diff --git a/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml b/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml new file mode 100644 index 0000000000000..7af5b5af2e7a1 --- /dev/null +++ b/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml @@ -0,0 +1,82 @@ +getData('customer_address'); +?> + +
+
escapeHtml(__('Additional Address Entries')) ?>
+
+ getAdditionalAddresses()): ?> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
escapeHtml($address->getFirstname()) ?>escapeHtml($address->getLastname()) ?>escapeHtml($block->getStreetAddress($address)) ?>escapeHtml($address->getCity()) ?>getCountryByCode($address->getCountryId()) ?>getRegion()->getRegion() ?>escapeHtml($address->getPostcode()) ?>escapeHtml($address->getTelephone()) ?> + escapeHtml(__('Edit')) ?> + escapeHtml(__('Delete')) ?> +
+
+ getChildHtml('pager')): ?> +
getChildHtml('pager') ?>
+ + +

escapeHtml(__('You have no other address entries in your address book.')) ?>

+ +
+
+ +
+
+ +
+ +
+ diff --git a/app/code/Magento/Customer/view/frontend/web/js/action/login.js b/app/code/Magento/Customer/view/frontend/web/js/action/login.js index 0015e2732e383..d75b8f70c5346 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/action/login.js +++ b/app/code/Magento/Customer/view/frontend/web/js/action/login.js @@ -31,11 +31,11 @@ define([ if (response.errors) { messageContainer.addErrorMessage(response); callbacks.forEach(function (callback) { - callback(loginData, response); + callback(loginData); }); } else { callbacks.forEach(function (callback) { - callback(loginData, response); + callback(loginData); }); customerData.invalidate(['customer']); diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index 39e3f8d95ee3b..66d37cb84e9cb 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -216,6 +216,9 @@ define([ $.cookieStorage.set(privateContentVersion, privateContent); } $.localStorage.set(privateContentVersion, privateContent); + _.each(dataProvider.getFromStorage(storage.keys()), function (sectionData, sectionName) { + buffer.notify(sectionName, sectionData); + }); this.reload([], false); isLoading = true; } else if (expiredSectionNames.length > 0) { diff --git a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js index 37bd3a19df638..c14a59af49706 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js +++ b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js @@ -84,6 +84,7 @@ define([ if (formElement.validation() && formElement.validation('isValid') ) { + this.isLoading(true); loginAction(loginData); } diff --git a/app/code/Magento/CustomerAnalytics/README.md b/app/code/Magento/CustomerAnalytics/README.md index 8c64ce97629da..9658a8e7d90ed 100644 --- a/app/code/Magento/CustomerAnalytics/README.md +++ b/app/code/Magento/CustomerAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_CustomerAnalytics module -The Magento_CustomerAnalytics module configures data definitions for a data collection related to the Customer module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_CustomerAnalytics module configures data definitions for a data collection related to the Customer module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php new file mode 100644 index 0000000000000..65672bcd3503b --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php @@ -0,0 +1,56 @@ +getAllowedAddressAttributes = $getAllowedAddressAttributes; + } + + /** + * Validate customer address create data + * + * @param array $addressData + * @return void + * @throws GraphQlInputException + */ + public function validate(array $addressData): void + { + $attributes = $this->getAllowedAddressAttributes->execute(); + $errorInput = []; + + foreach ($attributes as $attributeName => $attributeInfo) { + if ($attributeInfo->getIsRequired() + && (!isset($addressData[$attributeName]) || empty($addressData[$attributeName])) + ) { + $errorInput[] = $attributeName; + } + } + + if ($errorInput) { + throw new GraphQlInputException( + __('Required parameters are missing: %1', [implode(', ', $errorInput)]) + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php new file mode 100644 index 0000000000000..9640953032ac6 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php @@ -0,0 +1,127 @@ +serviceOutputProcessor = $serviceOutputProcessor; + $this->jsonSerializer = $jsonSerializer; + $this->customerResourceModel = $customerResourceModel; + $this->customerFactory = $customerFactory; + } + + /** + * Curate shipping and billing default options + * + * @param array $address + * @param AddressInterface $addressObject + * @return array + */ + private function curateAddressDefaultValues(array $address, AddressInterface $addressObject) : array + { + $customerModel = $this->customerFactory->create(); + $this->customerResourceModel->load($customerModel, $addressObject->getCustomerId()); + $address[CustomerInterface::DEFAULT_BILLING] = + ($customerModel->getDefaultBillingAddress() + && $addressObject->getId() == $customerModel->getDefaultBillingAddress()->getId()); + $address[CustomerInterface::DEFAULT_SHIPPING] = + ($customerModel->getDefaultShippingAddress() + && $addressObject->getId() == $customerModel->getDefaultShippingAddress()->getId()); + return $address; + } + + /** + * Transform single customer address data from object to in array format + * + * @param AddressInterface $addressObject + * @return array + */ + public function getAddressData(AddressInterface $addressObject): array + { + $address = $this->serviceOutputProcessor->process( + $addressObject, + AddressRepositoryInterface::class, + 'getById' + ); + $address = $this->curateAddressDefaultValues($address, $addressObject); + + if (isset($address[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY])) { + $address = array_merge($address, $address[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY]); + } + $customAttributes = []; + if (isset($address[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) { + foreach ($address[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES] as $attribute) { + $isArray = false; + if (is_array($attribute['value'])) { + $isArray = true; + foreach ($attribute['value'] as $attributeValue) { + if (is_array($attributeValue)) { + $customAttributes[$attribute['attribute_code']] = $this->jsonSerializer->serialize( + $attribute['value'] + ); + continue; + } + $customAttributes[$attribute['attribute_code']] = implode(',', $attribute['value']); + continue; + } + } + if ($isArray) { + continue; + } + $customAttributes[$attribute['attribute_code']] = $attribute['value']; + } + } + $address = array_merge($address, $customAttributes); + + return $address; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php new file mode 100644 index 0000000000000..13716b491fddf --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php @@ -0,0 +1,56 @@ +getAllowedAddressAttributes = $getAllowedAddressAttributes; + } + + /** + * Validate customer address update data + * + * @param array $addressData + * @return void + * @throws GraphQlInputException + */ + public function validate(array $addressData): void + { + $attributes = $this->getAllowedAddressAttributes->execute(); + $errorInput = []; + + foreach ($attributes as $attributeName => $attributeInfo) { + if ($attributeInfo->getIsRequired() + && (isset($addressData[$attributeName]) && empty($addressData[$attributeName])) + ) { + $errorInput[] = $attributeName; + } + } + + if ($errorInput) { + throw new GraphQlInputException( + __('Required parameters are missing: %1', [implode(', ', $errorInput)]) + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetAllowedAddressAttributes.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetAllowedAddressAttributes.php new file mode 100644 index 0000000000000..87be760732384 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetAllowedAddressAttributes.php @@ -0,0 +1,49 @@ +eavConfig = $eavConfig; + } + + /** + * Get allowed address attributes + * + * @return AbstractAttribute[] + */ + public function execute(): array + { + $attributes = $this->eavConfig->getEntityAttributes( + AddressMetadataManagementInterface::ENTITY_TYPE_ADDRESS + ); + foreach ($attributes as $attributeCode => $attribute) { + if (false === $attribute->getIsVisibleOnFront()) { + unset($attributes[$attributeCode]); + } + } + return $attributes; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php new file mode 100644 index 0000000000000..f7323402a6c62 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php @@ -0,0 +1,61 @@ +addressRepository = $addressRepository; + } + + /** + * Get customer address for user + * + * @param int $addressId + * @param int $userId + * @return AddressInterface + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + */ + public function execute(int $addressId, int $userId): AddressInterface + { + try { + /** @var AddressInterface $address */ + $address = $this->addressRepository->getById($addressId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Address id %1 does not exist.', [$addressId]) + ); + } + + if ($address->getCustomerId() != $userId) { + throw new GraphQlAuthorizationException( + __('Current customer does not have permission to address id %1', [$addressId]) + ); + } + return $address; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php index 646d426050b70..c8382593eab23 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php @@ -70,6 +70,25 @@ public function getCustomerById(int $customerId): array return $this->processCustomer($customer); } + /** + * Curate default shipping and default billing keys + * + * @param array $arrayAddress + * @return array + */ + private function curateAddressData(array $arrayAddress) : array + { + foreach ($arrayAddress as $key => $address) { + if (!isset($address['default_shipping'])) { + $arrayAddress[$key]['default_shipping'] = false; + } + if (!isset($address['default_billing'])) { + $arrayAddress[$key]['default_billing'] = false; + } + } + return $arrayAddress; + } + /** * Transform single customer data from object to in array format * @@ -83,6 +102,7 @@ private function processCustomer(CustomerInterface $customer): array CustomerRepositoryInterface::class, 'get' ); + $customerData['addresses'] = $this->curateAddressData($customerData['addresses']); if (isset($customerData['extension_attributes'])) { $customerData = array_merge($customerData, $customerData['extension_attributes']); } diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php similarity index 97% rename from app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php rename to app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php index 36365f1910f27..18510b872e64a 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php @@ -15,9 +15,9 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Update account information + * Update customer data */ -class UpdateAccountInformation +class UpdateCustomerData { /** * @var CustomerRepositoryInterface diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php new file mode 100644 index 0000000000000..823444e5a2d7d --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php @@ -0,0 +1,124 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->addressRepository = $addressRepository; + $this->addressInterfaceFactory = $addressInterfaceFactory; + $this->customerAddressDataProvider = $customerAddressDataProvider; + $this->dataObjectHelper = $dataObjectHelper; + $this->customerAddressCreateDataValidator = $customerAddressCreateDataValidator; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + $this->customerAddressCreateDataValidator->validate($args['input']); + + $address = $this->createCustomerAddress((int)$currentUserId, $args['input']); + return $this->customerAddressDataProvider->getAddressData($address); + } + + /** + * Create customer address + * + * @param int $customerId + * @param array $addressData + * @return AddressInterface + * @throws GraphQlInputException + */ + private function createCustomerAddress(int $customerId, array $addressData) : AddressInterface + { + /** @var AddressInterface $address */ + $address = $this->addressInterfaceFactory->create(); + $this->dataObjectHelper->populateWithArray($address, $addressData, AddressInterface::class); + $address->setCustomerId($customerId); + + try { + $address = $this->addressRepository->save($address); + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage()), $e); + } + return $address; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php new file mode 100644 index 0000000000000..084857c84d5a4 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php @@ -0,0 +1,96 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->addressRepository = $addressRepository; + $this->getCustomerAddressForUser = $getCustomerAddressForUser; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + return $this->deleteCustomerAddress((int)$currentUserId, (int)$args['id']); + } + + /** + * Delete customer address + * + * @param int $customerId + * @param int $addressId + * @return bool + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + */ + private function deleteCustomerAddress($customerId, $addressId) + { + $address = $this->getCustomerAddressForUser->execute($addressId, $customerId); + if ($address->isDefaultBilling()) { + throw new GraphQlAuthorizationException( + __('Customer Address %1 is set as default billing address and can not be deleted', [$addressId]) + ); + } + if ($address->isDefaultShipping()) { + throw new GraphQlAuthorizationException( + __('Customer Address %1 is set as default shipping address and can not be deleted', [$addressId]) + ); + } + return $this->addressRepository->delete($address); + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php index 5dc857f3f178c..50760d2e2e31c 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php @@ -9,7 +9,7 @@ use Magento\CustomerGraphQl\Model\Customer\ChangeSubscriptionStatus; use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; -use Magento\CustomerGraphQl\Model\Customer\UpdateAccountInformation; +use Magento\CustomerGraphQl\Model\Customer\UpdateCustomerData; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; @@ -27,9 +27,9 @@ class UpdateCustomer implements ResolverInterface private $checkCustomerAccount; /** - * @var UpdateAccountInformation + * @var UpdateCustomerData */ - private $updateAccountInformation; + private $updateCustomerData; /** * @var ChangeSubscriptionStatus @@ -43,18 +43,18 @@ class UpdateCustomer implements ResolverInterface /** * @param CheckCustomerAccount $checkCustomerAccount - * @param UpdateAccountInformation $updateAccountInformation + * @param UpdateCustomerData $updateCustomerData * @param ChangeSubscriptionStatus $changeSubscriptionStatus * @param CustomerDataProvider $customerDataProvider */ public function __construct( CheckCustomerAccount $checkCustomerAccount, - UpdateAccountInformation $updateAccountInformation, + UpdateCustomerData $updateCustomerData, ChangeSubscriptionStatus $changeSubscriptionStatus, CustomerDataProvider $customerDataProvider ) { $this->checkCustomerAccount = $checkCustomerAccount; - $this->updateAccountInformation = $updateAccountInformation; + $this->updateCustomerData = $updateCustomerData; $this->changeSubscriptionStatus = $changeSubscriptionStatus; $this->customerDataProvider = $customerDataProvider; } @@ -79,7 +79,7 @@ public function resolve( $this->checkCustomerAccount->execute($currentUserId, $currentUserType); $currentUserId = (int)$currentUserId; - $this->updateAccountInformation->execute($currentUserId, $args['input']); + $this->updateCustomerData->execute($currentUserId, $args['input']); if (isset($args['input']['is_subscribed'])) { $this->changeSubscriptionStatus->execute($currentUserId, (bool)$args['input']['is_subscribed']); diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php new file mode 100644 index 0000000000000..7bae40e4cc5de --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php @@ -0,0 +1,114 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->addressRepository = $addressRepository; + $this->customerAddressDataProvider = $customerAddressDataProvider; + $this->dataObjectHelper = $dataObjectHelper; + $this->customerAddressUpdateDataValidator = $customerAddressUpdateDataValidator; + $this->getCustomerAddressForUser = $getCustomerAddressForUser; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + $this->customerAddressUpdateDataValidator->validate($args['input']); + + $address = $this->updateCustomerAddress((int)$currentUserId, (int)$args['id'], $args['input']); + return $this->customerAddressDataProvider->getAddressData($address); + } + + /** + * Update customer address + * + * @param int $customerId + * @param int $addressId + * @param array $addressData + * @return AddressInterface + */ + private function updateCustomerAddress(int $customerId, int $addressId, array $addressData) + { + $address = $this->getCustomerAddressForUser->execute($addressId, $customerId); + $this->dataObjectHelper->populateWithArray($address, $addressData, AddressInterface::class); + return $this->addressRepository->save($address); + } +} diff --git a/app/code/Magento/CustomerGraphQl/composer.json b/app/code/Magento/CustomerGraphQl/composer.json index 488013c1ee586..722aba3aa0658 100644 --- a/app/code/Magento/CustomerGraphQl/composer.json +++ b/app/code/Magento/CustomerGraphQl/composer.json @@ -6,6 +6,7 @@ "php": "~7.1.3||~7.2.0", "magento/module-customer": "*", "magento/module-authorization": "*", + "magento/module-eav": "*", "magento/module-newsletter": "*", "magento/module-integration": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CustomerGraphQl/etc/module.xml b/app/code/Magento/CustomerGraphQl/etc/module.xml index bde93c6276500..eeed4862bbbfd 100644 --- a/app/code/Magento/CustomerGraphQl/etc/module.xml +++ b/app/code/Magento/CustomerGraphQl/etc/module.xml @@ -6,11 +6,5 @@ */ --> - - - - - - - + diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index ca4ac6cc245a7..c92753b96225c 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -10,6 +10,40 @@ type Mutation { changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes the password for the logged-in customer") updateCustomer (input: UpdateCustomerInput!): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information") revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token") + createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address") + updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address") + deleteCustomerAddress(id: Int!): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete customer address") +} + +input CustomerAddressInput { + firstname: String @doc(description: "The first name of the person associated with the shipping/billing address") + lastname: String @doc(description: "The family name of the person associated with the shipping/billing address") + company: String @doc(description: "The customer's company") + telephone: String @doc(description: "The telephone number") + street: [String] @doc(description: "An array of strings that define the street number and name") + city: String @doc(description: "The city or town") + region: CustomerAddressRegionInput @doc(description: "An object containing the region name, region code, and region ID") + postcode: String @doc(description: "The customer's ZIP or postal code") + country_id: CountryCodeEnum @doc(description: "The customer's country") + default_shipping: Boolean @doc(description: "Indicates whether the address is the default shipping address") + default_billing: Boolean @doc(description: "Indicates whether the address is the default billing address") + fax: String @doc(description: "The fax number") + middlename: String @doc(description: "The middle name of the person associated with the shipping/billing address") + prefix: String @doc(description: "An honorific, such as Dr., Mr., or Mrs.") + suffix: String @doc(description: "A value such as Sr., Jr., or III") + vat_id: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") + custom_attributes: [CustomerAddressAttributeInput] @doc(description: "Address custom attributes") +} + +input CustomerAddressRegionInput @doc(description: "CustomerAddressRegionInput defines the customer's state or province") { + region_code: String @doc(description: "The address region code") + region: String @doc(description: "The state or province name") + region_id: Int @doc(description: "Uniquely identifies the region") +} + +input CustomerAddressAttributeInput { + attribute_code: String! @doc(description: "Attribute code") + value: String! @doc(description: "Attribute value") } type CustomerToken { @@ -70,6 +104,8 @@ type CustomerAddress @doc(description: "CustomerAddress contains detailed inform vat_id: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") default_shipping: Boolean @doc(description: "Indicates whether the address is the default shipping address") default_billing: Boolean @doc(description: "Indicates whether the address is the default billing address") + custom_attributes: [CustomerAddressAttribute] @doc(description: "Address custom attributes") + extension_attributes: [CustomerAddressAttribute] @doc(description: "Address extension attributes") } type CustomerAddressRegion @doc(description: "CustomerAddressRegion defines the customer's state or province") { @@ -77,3 +113,256 @@ type CustomerAddressRegion @doc(description: "CustomerAddressRegion defines the region: String @doc(description: "The state or province name") region_id: Int @doc(description: "Uniquely identifies the region") } + +type CustomerAddressAttribute { + attribute_code: String @doc(description: "Attribute code") + value: String @doc(description: "Attribute value") +} + +enum CountryCodeEnum @doc(description: "The list of countries codes") { + AF @doc(description: "Afghanistan") + AX @doc(description: "Åland Islands") + AL @doc(description: "Albania") + DZ @doc(description: "Algeria") + AS @doc(description: "American Samoa") + AD @doc(description: "Andorra") + AO @doc(description: "Angola") + AI @doc(description: "Anguilla") + AQ @doc(description: "Antarctica") + AG @doc(description: "Antigua & Barbuda") + AR @doc(description: "Argentina") + AM @doc(description: "Armenia") + AW @doc(description: "Aruba") + AU @doc(description: "Australia") + AT @doc(description: "Austria") + AZ @doc(description: "Azerbaijan") + BS @doc(description: "Bahamas") + BH @doc(description: "Bahrain") + BD @doc(description: "Bangladesh") + BB @doc(description: "Barbados") + BY @doc(description: "Belarus") + BE @doc(description: "Belgium") + BZ @doc(description: "Belize") + BJ @doc(description: "Benin") + BM @doc(description: "Bermuda") + BT @doc(description: "Bhutan") + BO @doc(description: "Bolivia") + BA @doc(description: "Bosnia & Herzegovina") + BW @doc(description: "Botswana") + BV @doc(description: "Bouvet Island") + BR @doc(description: "Brazil") + IO @doc(description: "British Indian Ocean Territory") + VG @doc(description: "British Virgin Islands") + BN @doc(description: "Brunei") + BG @doc(description: "Bulgaria") + BF @doc(description: "Burkina Faso") + BI @doc(description: "Burundi") + KH @doc(description: "Cambodia") + CM @doc(description: "Cameroon") + CA @doc(description: "Canada") + CV @doc(description: "Cape Verde") + KY @doc(description: "Cayman Islands") + CF @doc(description: "Central African Republic") + TD @doc(description: "Chad") + CL @doc(description: "Chile") + CN @doc(description: "China") + CX @doc(description: "Christmas Island") + CC @doc(description: "Cocos (Keeling) Islands") + CO @doc(description: "Colombia") + KM @doc(description: "Comoros") + CG @doc(description: "Congo -Brazzaville") + CD @doc(description: "Congo - Kinshasa") + CK @doc(description: "Cook Islands") + CR @doc(description: "Costa Rica") + CI @doc(description: "Côte d’Ivoire") + HR @doc(description: "Croatia") + CU @doc(description: "Cuba") + CY @doc(description: "Cyprus") + CZ @doc(description: "Czech Republic") + DK @doc(description: "Denmark") + DJ @doc(description: "Djibouti") + DM @doc(description: "Dominica") + DO @doc(description: "Dominican Republic") + EC @doc(description: "Ecuador") + EG @doc(description: "Egypt") + SV @doc(description: "El Salvador") + GQ @doc(description: "Equatorial Guinea") + ER @doc(description: "Eritrea") + EE @doc(description: "Estonia") + ET @doc(description: "Ethiopia") + FK @doc(description: "Falkland Islands") + FO @doc(description: "Faroe Islands") + FJ @doc(description: "Fiji") + FI @doc(description: "Finland") + FR @doc(description: "France") + GF @doc(description: "French Guiana") + PF @doc(description: "French Polynesia") + TF @doc(description: "French Southern Territories") + GA @doc(description: "Gabon") + GM @doc(description: "Gambia") + GE @doc(description: "Georgia") + DE @doc(description: "Germany") + GH @doc(description: "Ghana") + GI @doc(description: "Gibraltar") + GR @doc(description: "Greece") + GL @doc(description: "Greenland") + GD @doc(description: "Grenada") + GP @doc(description: "Guadeloupe") + GU @doc(description: "Guam") + GT @doc(description: "Guatemala") + GG @doc(description: "Guernsey") + GN @doc(description: "Guinea") + GW @doc(description: "Guinea-Bissau") + GY @doc(description: "Guyana") + HT @doc(description: "Haiti") + HM @doc(description: "Heard & McDonald Islands") + HN @doc(description: "Honduras") + HK @doc(description: "Hong Kong SAR China") + HU @doc(description: "Hungary") + IS @doc(description: "Iceland") + IN @doc(description: "India") + ID @doc(description: "Indonesia") + IR @doc(description: "Iran") + IQ @doc(description: "Iraq") + IE @doc(description: "Ireland") + IM @doc(description: "Isle of Man") + IL @doc(description: "Israel") + IT @doc(description: "Italy") + JM @doc(description: "Jamaica") + JP @doc(description: "Japan") + JE @doc(description: "Jersey") + JO @doc(description: "Jordan") + KZ @doc(description: "Kazakhstan") + KE @doc(description: "Kenya") + KI @doc(description: "Kiribati") + KW @doc(description: "Kuwait") + KG @doc(description: "Kyrgyzstan") + LA @doc(description: "Laos") + LV @doc(description: "Latvia") + LB @doc(description: "Lebanon") + LS @doc(description: "Lesotho") + LR @doc(description: "Liberia") + LY @doc(description: "Libya") + LI @doc(description: "Liechtenstein") + LT @doc(description: "Lithuania") + LU @doc(description: "Luxembourg") + MO @doc(description: "Macau SAR China") + MK @doc(description: "Macedonia") + MG @doc(description: "Madagascar") + MW @doc(description: "Malawi") + MY @doc(description: "Malaysia") + MV @doc(description: "Maldives") + ML @doc(description: "Mali") + MT @doc(description: "Malta") + MH @doc(description: "Marshall Islands") + MQ @doc(description: "Martinique") + MR @doc(description: "Mauritania") + MU @doc(description: "Mauritius") + YT @doc(description: "Mayotte") + MX @doc(description: "Mexico") + FM @doc(description: "Micronesia") + MD @doc(description: "Moldova") + MC @doc(description: "Monaco") + MN @doc(description: "Mongolia") + ME @doc(description: "Montenegro") + MS @doc(description: "Montserrat") + MA @doc(description: "Morocco") + MZ @doc(description: "Mozambique") + MM @doc(description: "Myanmar (Burma)") + NA @doc(description: "Namibia") + NR @doc(description: "Nauru") + NP @doc(description: "Nepal") + NL @doc(description: "Netherlands") + AN @doc(description: "Netherlands Antilles") + NC @doc(description: "New Caledonia") + NZ @doc(description: "New Zealand") + NI @doc(description: "Nicaragua") + NE @doc(description: "Niger") + NG @doc(description: "Nigeria") + NU @doc(description: "Niue") + NF @doc(description: "Norfolk Island") + MP @doc(description: "Northern Mariana Islands") + KP @doc(description: "North Korea") + NO @doc(description: "Norway") + OM @doc(description: "Oman") + PK @doc(description: "Pakistan") + PW @doc(description: "Palau") + PS @doc(description: "Palestinian Territories") + PA @doc(description: "Panama") + PG @doc(description: "Papua New Guinea") + PY @doc(description: "Paraguay") + PE @doc(description: "Peru") + PH @doc(description: "Philippines") + PN @doc(description: "Pitcairn Islands") + PL @doc(description: "Poland") + PT @doc(description: "Portugal") + QA @doc(description: "Qatar") + RE @doc(description: "Réunion") + RO @doc(description: "Romania") + RU @doc(description: "Russia") + RW @doc(description: "Rwanda") + WS @doc(description: "Samoa") + SM @doc(description: "San Marino") + ST @doc(description: "São Tomé & Príncipe") + SA @doc(description: "Saudi Arabia") + SN @doc(description: "Senegal") + RS @doc(description: "Serbia") + SC @doc(description: "Seychelles") + SL @doc(description: "Sierra Leone") + SG @doc(description: "Singapore") + SK @doc(description: "Slovakia") + SI @doc(description: "Slovenia") + SB @doc(description: "Solomon Islands") + SO @doc(description: "Somalia") + ZA @doc(description: "South Africa") + GS @doc(description: "South Georgia & South Sandwich Islands") + KR @doc(description: "South Korea") + ES @doc(description: "Spain") + LK @doc(description: "Sri Lanka") + BL @doc(description: "St. Barthélemy") + SH @doc(description: "St. Helena") + KN @doc(description: "St. Kitts & Nevis") + LC @doc(description: "St. Lucia") + MF @doc(description: "St. Martin") + PM @doc(description: "St. Pierre & Miquelon") + VC @doc(description: "St. Vincent & Grenadines") + SD @doc(description: "Sudan") + SR @doc(description: "Suriname") + SJ @doc(description: "Svalbard & Jan Mayen") + SZ @doc(description: "Swaziland") + SE @doc(description: "Sweden") + CH @doc(description: "Switzerland") + SY @doc(description: "Syria") + TW @doc(description: "Taiwan") + TJ @doc(description: "Tajikistan") + TZ @doc(description: "Tanzania") + TH @doc(description: "Thailand") + TL @doc(description: "Timor-Leste") + TG @doc(description: "Togo") + TK @doc(description: "Tokelau") + TO @doc(description: "Tonga") + TT @doc(description: "Trinidad & Tobago") + TN @doc(description: "Tunisia") + TR @doc(description: "Turkey") + TM @doc(description: "Turkmenistan") + TC @doc(description: "Turks & Caicos Islands") + TV @doc(description: "Tuvalu") + UG @doc(description: "Uganda") + UA @doc(description: "Ukraine") + AE @doc(description: "United Arab Emirates") + GB @doc(description: "United Kingdom") + US @doc(description: "United States") + UY @doc(description: "Uruguay") + UM @doc(description: "U.S. Outlying Islands") + VI @doc(description: "U.S. Virgin Islands") + UZ @doc(description: "Uzbekistan") + VU @doc(description: "Vanuatu") + VA @doc(description: "Vatican City") + VE @doc(description: "Venezuela") + VN @doc(description: "Vietnam") + WF @doc(description: "Wallis & Futuna") + EH @doc(description: "Western Sahara") + YE @doc(description: "Yemen") + ZM @doc(description: "Zambia") + ZW @doc(description: "Zimbabwe") +} \ No newline at end of file diff --git a/app/code/Magento/Deploy/Collector/Collector.php b/app/code/Magento/Deploy/Collector/Collector.php index b02c8e478d639..5974297a76cc7 100644 --- a/app/code/Magento/Deploy/Collector/Collector.php +++ b/app/code/Magento/Deploy/Collector/Collector.php @@ -8,6 +8,8 @@ use Magento\Deploy\Source\SourcePool; use Magento\Deploy\Package\Package; use Magento\Deploy\Package\PackageFactory; +use Magento\Deploy\Package\PackageFile; +use Magento\Framework\Module\Manager; use Magento\Framework\View\Asset\PreProcessor\FileNameResolver; /** @@ -43,6 +45,9 @@ class Collector implements CollectorInterface * @var PackageFactory */ private $packageFactory; + + /** @var \Magento\Framework\Module\Manager */ + private $moduleManager; /** * Default values for package primary identifiers @@ -61,15 +66,19 @@ class Collector implements CollectorInterface * @param SourcePool $sourcePool * @param FileNameResolver $fileNameResolver * @param PackageFactory $packageFactory + * @param Manager|null $moduleManager */ public function __construct( SourcePool $sourcePool, FileNameResolver $fileNameResolver, - PackageFactory $packageFactory + PackageFactory $packageFactory, + Manager $moduleManager = null ) { $this->sourcePool = $sourcePool; $this->fileNameResolver = $fileNameResolver; $this->packageFactory = $packageFactory; + $this->moduleManager = $moduleManager ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Module\Manager::class); } /** @@ -81,19 +90,11 @@ public function collect() foreach ($this->sourcePool->getAll() as $source) { $files = $source->get(); foreach ($files as $file) { - $file->setDeployedFileName($this->fileNameResolver->resolve($file->getFileName())); - $params = [ - 'area' => $file->getArea(), - 'theme' => $file->getTheme(), - 'locale' => $file->getLocale(), - 'module' => $file->getModule(), - 'isVirtual' => (!$file->getLocale() || !$file->getTheme() || !$file->getArea()) - ]; - foreach ($this->packageDefaultValues as $name => $value) { - if (!isset($params[$name])) { - $params[$name] = $value; - } + if ($file->getModule() && !$this->moduleManager->isEnabled($file->getModule())) { + continue; } + $file->setDeployedFileName($this->fileNameResolver->resolve($file->getFileName())); + $params = $this->getParams($file); $packagePath = "{$params['area']}/{$params['theme']}/{$params['locale']}"; if (!isset($packages[$packagePath])) { $packages[$packagePath] = $this->packageFactory->create($params); @@ -105,4 +106,27 @@ public function collect() } return $packages; } + + /** + * Retrieve package params + * + * @param PackageFile $file + * @return array + */ + private function getParams(PackageFile $file) + { + $params = [ + 'area' => $file->getArea(), + 'theme' => $file->getTheme(), + 'locale' => $file->getLocale(), + 'module' => $file->getModule(), + 'isVirtual' => (!$file->getLocale() || !$file->getTheme() || !$file->getArea()) + ]; + foreach ($this->packageDefaultValues as $name => $value) { + if (!isset($params[$name])) { + $params[$name] = $value; + } + } + return $params; + } } diff --git a/app/code/Magento/Deploy/Model/Filesystem.php b/app/code/Magento/Deploy/Model/Filesystem.php index 59880d2d669b4..e7d4d31e6f144 100644 --- a/app/code/Magento/Deploy/Model/Filesystem.php +++ b/app/code/Magento/Deploy/Model/Filesystem.php @@ -29,8 +29,8 @@ class Filesystem * Access permissions to the files are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ const PERMISSIONS_FILE = 0640; @@ -41,8 +41,8 @@ class Filesystem * Access permissions to the directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ const PERMISSIONS_DIR = 0750; @@ -320,8 +320,8 @@ public function cleanupFilesystem($directoryCodeList) * Access permissions to the files and directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files and directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ protected function changePermissions($directoryCodeList, $dirPermissions, $filePermissions) { @@ -345,8 +345,8 @@ protected function changePermissions($directoryCodeList, $dirPermissions, $fileP * Access permissions to the files and directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files and directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ public function lockStaticResources() { diff --git a/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php b/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php index 4e2758b362a43..97d22633af03c 100644 --- a/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php +++ b/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Directory\Model\Config\Source; /** @@ -14,10 +15,23 @@ class WeightUnit implements \Magento\Framework\Option\ArrayInterface { /** - * {@inheritdoc} + * @var string + */ + const CODE_LBS = 'lbs'; + + /** + * @var string + */ + const CODE_KGS = 'kgs'; + + /** + * @inheritdoc */ public function toOptionArray() { - return [['value' => 'lbs', 'label' => __('lbs')], ['value' => 'kgs', 'label' => __('kgs')]]; + return [ + ['value' => self::CODE_LBS, 'label' => __('lbs')], + ['value' => self::CODE_KGS, 'label' => __('kgs')] + ]; } } diff --git a/app/code/Magento/Directory/Model/PriceCurrency.php b/app/code/Magento/Directory/Model/PriceCurrency.php index a211242d377f3..08d320abd8b53 100644 --- a/app/code/Magento/Directory/Model/PriceCurrency.php +++ b/app/code/Magento/Directory/Model/PriceCurrency.php @@ -46,7 +46,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function convert($amount, $scope = null, $currency = null) { @@ -58,17 +58,15 @@ public function convert($amount, $scope = null, $currency = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function convertAndRound($amount, $scope = null, $currency = null, $precision = self::DEFAULT_PRECISION) { - $currentCurrency = $this->getCurrency($scope, $currency); - $convertedValue = $this->getStore($scope)->getBaseCurrency()->convert($amount, $currentCurrency); - return round($convertedValue, $precision); + return $this->roundPrice($this->convert($amount, $scope, $currency), $precision); } /** - * {@inheritdoc} + * @inheritdoc */ public function format( $amount, @@ -82,7 +80,7 @@ public function format( } /** - * {@inheritdoc} + * @inheritdoc */ public function convertAndFormat( $amount, @@ -97,7 +95,7 @@ public function convertAndFormat( } /** - * {@inheritdoc} + * @inheritdoc */ public function getCurrency($scope = null, $currency = null) { @@ -118,6 +116,8 @@ public function getCurrency($scope = null, $currency = null) } /** + * Get currrency symbol + * * @param null|string|bool|int|\Magento\Framework\App\ScopeInterface $scope * @param \Magento\Framework\Model\AbstractModel|string|null $currency * @return string @@ -148,13 +148,22 @@ protected function getStore($scope = null) } /** - * Round price + * @inheritdoc + */ + public function round($price) + { + return round($price, 2); + } + + /** + * Round price with precision * * @param float $price + * @param int $precision * @return float */ - public function round($price) + public function roundPrice($price, $precision = self::DEFAULT_PRECISION) { - return round($price, 2); + return round($price, $precision); } } diff --git a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php index 20140baae01b6..f90a7b3b519b5 100644 --- a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php +++ b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php @@ -129,8 +129,8 @@ public function __construct( /** * Add top destinition countries to head of option array * - * @param $emptyLabel - * @param $options + * @param string $emptyLabel + * @param array $options * @return array */ private function addForegroundCountriesToOptionArray($emptyLabel, $options) @@ -207,7 +207,7 @@ public function getItemById($countryId) * Add filter by country code to collection. * $countryCode can be either array of country codes or string representing one country code. * $iso can be either array containing 'iso2', 'iso3' values or string with containing one of that values directly. - * The collection will contain countries where at least one of contry $iso fields matches $countryCode. + * The collection will contain countries where at least one of country $iso fields matches $countryCode. * * @param string|string[] $countryCode * @param string|string[] $iso diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Crosssell.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Crosssell.php index e7b1fff27680e..85f9ad39c4df5 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Crosssell.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Crosssell.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class Crosssell extends \Magento\Catalog\Controller\Adminhtml\Product\Crosssell +use Magento\Catalog\Controller\Adminhtml\Product\Crosssell as CatalogCrosssell; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Crosssell + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class Crosssell extends CatalogCrosssell implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/CrosssellGrid.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/CrosssellGrid.php index 8a77cb2c01011..b6c25bdec9f14 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/CrosssellGrid.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/CrosssellGrid.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class CrosssellGrid extends \Magento\Catalog\Controller\Adminhtml\Product\CrosssellGrid +use Magento\Catalog\Controller\Adminhtml\Product\CrosssellGrid as CatalogCrosssellGrid; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class CrosssellGrid + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since cross-sell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml + */ +class CrosssellGrid extends CatalogCrosssellGrid implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Related.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Related.php index 8c10377f74e83..0352d97bca93a 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Related.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Related.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class Related extends \Magento\Catalog\Controller\Adminhtml\Product\Related +use Magento\Catalog\Controller\Adminhtml\Product\Related as CatalogRelated; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Related + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class Related extends CatalogRelated implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/RelatedGrid.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/RelatedGrid.php index 403b3828e39a0..11e9c4eb5e3af 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/RelatedGrid.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/RelatedGrid.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class RelatedGrid extends \Magento\Catalog\Controller\Adminhtml\Product\RelatedGrid +use Magento\Catalog\Controller\Adminhtml\Product\RelatedGrid as CatalogRelatedGrid; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class RelatedGrid + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since related products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml + */ +class RelatedGrid extends CatalogRelatedGrid implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Upsell.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Upsell.php index d712a7ec80d18..a8581f38c419b 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Upsell.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Upsell.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class Upsell extends \Magento\Catalog\Controller\Adminhtml\Product\Upsell +use Magento\Catalog\Controller\Adminhtml\Product\Upsell as CatalogUpsell; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class Upsell + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ +class Upsell extends CatalogUpsell implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/UpsellGrid.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/UpsellGrid.php index 960909ca1b399..3e28a96948a77 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/UpsellGrid.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/UpsellGrid.php @@ -6,6 +6,16 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; -class UpsellGrid extends \Magento\Catalog\Controller\Adminhtml\Product\UpsellGrid +use Magento\Catalog\Controller\Adminhtml\Product\UpsellGrid as CatalogUpsellGrid; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Class UpsellGrid + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + * @deprecated Not used since upsell products grid moved to UI components. + * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml + */ +class UpsellGrid extends CatalogUpsellGrid implements HttpPostActionInterface { } diff --git a/app/code/Magento/Downloadable/Setup/Patch/Data/UpdateLinksExistDefaultAttributeValue.php b/app/code/Magento/Downloadable/Setup/Patch/Data/UpdateLinksExistDefaultAttributeValue.php new file mode 100644 index 0000000000000..bde9accc39d79 --- /dev/null +++ b/app/code/Magento/Downloadable/Setup/Patch/Data/UpdateLinksExistDefaultAttributeValue.php @@ -0,0 +1,82 @@ +moduleDataSetup = $moduleDataSetup; + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); + // remove default value + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'links_exist', + 'default_value', + null + ); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return [InstallDownloadableAttributes::class]; + } + + /** + * @inheritdoc + */ + public static function getVersion() + { + return '2.0.3'; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php b/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php index 8bd65ba85abef..be4b5a3fa0fe5 100644 --- a/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php +++ b/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php @@ -17,7 +17,7 @@ interface AttributeSetRepositoryInterface * Retrieve list of Attribute Sets * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#AttributeSetRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#AttributeSetRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index c605f3ce17e30..06a4abb985802 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Eav\Model\Entity; use Magento\Framework\Api\AttributeValueFactory; @@ -295,6 +294,12 @@ public function beforeSave() } } + if ($this->getFrontendInput() == 'media_image') { + if (!$this->getFrontendModel()) { + $this->setFrontendModel(\Magento\Catalog\Model\Product\Attribute\Frontend\Image::class); + } + } + if ($this->getBackendType() == 'gallery') { if (!$this->getBackendModel()) { $this->setBackendModel(\Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend::class); @@ -316,7 +321,7 @@ public function afterSave() } /** - * @return $this + * @inheritdoc * @since 100.0.7 */ public function afterDelete() diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 29ead9ff810e5..7ed455eccf4e0 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.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\Entity\Attribute; @@ -418,7 +419,7 @@ public function getIsVisibleOnFront() /** * Returns default value * - * @return string|int|bool|float + * @return string|null * @codeCoverageIgnore */ public function getDefaultValue() @@ -812,7 +813,7 @@ public function getBackendTable() if ($this->isStatic()) { $this->_dataTable = $this->getEntityType()->getValueTablePrefix(); } else { - $backendTable = trim($this->_getData('backend_table')); + $backendTable = trim((string)$this->_getData('backend_table')); if (empty($backendTable)) { $entityTable = [$this->getEntityType()->getEntityTablePrefix(), $this->getBackendType()]; $backendTable = $this->getResource()->getTable($entityTable); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php index 53c32e1cbfba4..86ab6f6ea6e9f 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.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\Entity\Attribute\Backend; @@ -204,12 +205,12 @@ public function getEntityValueId($entity) /** * Retrieve default value * - * @return mixed + * @return string */ public function getDefaultValue() { if ($this->_defaultValue === null) { - if ($this->getAttribute()->getDefaultValue()) { + if ($this->getAttribute()->getDefaultValue() !== null) { $this->_defaultValue = $this->getAttribute()->getDefaultValue(); } else { $this->_defaultValue = ""; @@ -285,7 +286,7 @@ public function afterLoad($object) public function beforeSave($object) { $attrCode = $this->getAttribute()->getAttributeCode(); - if (!$object->hasData($attrCode) && $this->getDefaultValue()) { + if (!$object->hasData($attrCode) && $this->getDefaultValue() !== '') { $object->setData($attrCode, $this->getDefaultValue()); } @@ -348,9 +349,8 @@ public function getAffectedFields($object) } /** - * By default attribute value is considered scalar that can be stored in a generic way + * By default attribute value is considered scalar that can be stored in a generic way {@inheritdoc} * - * {@inheritdoc} * @codeCoverageIgnore */ public function isScalar() diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index b10d30e48064a..6a42e4b3c9fe2 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -416,7 +416,7 @@ - 2147483647 + 10000 2147483647 diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index 28d2d171eecf4..90a4e6571c9b6 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -87,7 +87,7 @@ public function __construct( public function sendMessage() { try { - $zendMessage = Message::fromString($this->message->getRawMessage()); + $zendMessage = Message::fromString($this->message->getRawMessage())->setEncoding('utf-8'); if (2 === $this->isSetReturnPath && $this->returnPathValue) { $zendMessage->setSender($this->returnPathValue); } elseif (1 === $this->isSetReturnPath && $zendMessage->getFrom()->count()) { diff --git a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php index aae30026b2b77..9331b68675b67 100644 --- a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php +++ b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php @@ -8,12 +8,18 @@ namespace Magento\EncryptionKey\Setup\Patch\Data; use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\App\ObjectManager; /** * Migrate encrypted configuration values to the latest cipher */ class SodiumChachaPatch implements DataPatchInterface { + /** + * @var \Magento\Framework\Config\ScopeInterface + */ + private $scope; + /** * @var \Magento\Framework\Setup\ModuleDataSetupInterface */ @@ -35,25 +41,29 @@ class SodiumChachaPatch implements DataPatchInterface private $state; /** + * SodiumChachaPatch constructor. * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup * @param \Magento\Config\Model\Config\Structure\Proxy $structure * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor * @param \Magento\Framework\App\State $state + * @param \Magento\Framework\Config\ScopeInterface|null $scope */ public function __construct( \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup, \Magento\Config\Model\Config\Structure\Proxy $structure, \Magento\Framework\Encryption\EncryptorInterface $encryptor, - \Magento\Framework\App\State $state + \Magento\Framework\App\State $state, + \Magento\Framework\Config\ScopeInterface $scope = null ) { $this->moduleDataSetup = $moduleDataSetup; $this->structure = $structure; $this->encryptor = $encryptor; $this->state = $state; + $this->scope = $scope ?? ObjectManager::getInstance()->get(\Magento\Framework\Config\ScopeInterface::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -65,7 +75,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -73,41 +83,67 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { return []; } + /** + * Re encrypt sensitive data in the system configuration + */ private function reEncryptSystemConfigurationValues() { - $structure = $this->structure; - $paths = $this->state->emulateAreaCode( - \Magento\Framework\App\Area::AREA_ADMINHTML, - function () use ($structure) { - return $structure->getFieldPathsByAttribute( - 'backend_model', - \Magento\Config\Model\Config\Backend\Encrypted::class - ); - } + $table = $this->moduleDataSetup->getTable('core_config_data'); + $hasEncryptedData = $this->moduleDataSetup->getConnection()->fetchOne( + $this->moduleDataSetup->getConnection() + ->select() + ->from($table, [new \Zend_Db_Expr('count(value)')]) + ->where('value LIKE ?', '0:2%') ); - // walk through found data and re-encrypt it - if ($paths) { - $table = $this->moduleDataSetup->getTable('core_config_data'); - $values = $this->moduleDataSetup->getConnection()->fetchPairs( - $this->moduleDataSetup->getConnection() - ->select() - ->from($table, ['config_id', 'value']) - ->where('path IN (?)', $paths) - ->where('value NOT LIKE ?', '') + if ($hasEncryptedData !== '0') { + $currentScope = $this->scope->getCurrentScope(); + $structure = $this->structure; + $paths = $this->state->emulateAreaCode( + \Magento\Framework\App\Area::AREA_ADMINHTML, + function () use ($structure) { + $this->scope->setCurrentScope(\Magento\Framework\App\Area::AREA_ADMINHTML); + /** Returns list of structure paths to be re encrypted */ + $paths = $structure->getFieldPathsByAttribute( + 'backend_model', + \Magento\Config\Model\Config\Backend\Encrypted::class + ); + /** Returns list of mapping between configPath => [structurePaths] */ + $mappedPaths = $structure->getFieldPaths(); + foreach ($mappedPaths as $mappedPath => $data) { + foreach ($data as $structurePath) { + if ($structurePath !== $mappedPath && $key = array_search($structurePath, $paths)) { + $paths[$key] = $mappedPath; + } + } + } + + return array_unique($paths); + } ); - foreach ($values as $configId => $value) { - $this->moduleDataSetup->getConnection()->update( - $table, - ['value' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], - ['config_id = ?' => (int)$configId] + $this->scope->setCurrentScope($currentScope); + // walk through found data and re-encrypt it + if ($paths) { + $values = $this->moduleDataSetup->getConnection()->fetchPairs( + $this->moduleDataSetup->getConnection() + ->select() + ->from($table, ['config_id', 'value']) + ->where('path IN (?)', $paths) + ->where('value NOT LIKE ?', '') ); + foreach ($values as $configId => $value) { + $this->moduleDataSetup->getConnection()->update( + $table, + ['value' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], + ['config_id = ?' => (int)$configId] + ); + } } } } diff --git a/app/code/Magento/Fedex/Model/Carrier.php b/app/code/Magento/Fedex/Model/Carrier.php index f6e63e04ac559..955345851e67a 100644 --- a/app/code/Magento/Fedex/Model/Carrier.php +++ b/app/code/Magento/Fedex/Model/Carrier.php @@ -986,6 +986,7 @@ public function getCode($type, $code = '') * Return FeDex currency ISO code by Magento Base Currency Code * * @return string 3-digit currency code + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getCurrencyCode() { @@ -1008,7 +1009,7 @@ public function getCurrencyCode() ]; $currencyCode = $this->_storeManager->getStore()->getBaseCurrencyCode(); - return isset($codes[$currencyCode]) ? $codes[$currencyCode] : $currencyCode; + return $codes[$currencyCode] ?? $currencyCode; } /** @@ -1438,6 +1439,8 @@ protected function _doShipmentRequest(\Magento\Framework\DataObject $request) } /** + * Return Tracking Number + * * @param array|object $trackingIds * @return string */ @@ -1452,10 +1455,10 @@ function ($val) { } /** - * For multi package shipments. Delete requested shipments if the current shipment - * request is failed + * For multi package shipments. Delete requested shipments if the current shipment request is failed * * @param array $data + * * @return bool */ public function rollBack($data) @@ -1475,6 +1478,7 @@ public function rollBack($data) * Return container types of carrier * * @param \Magento\Framework\DataObject|null $params + * * @return array|bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -1542,6 +1546,7 @@ public function getContainerTypesFilter() * Return delivery confirmation types of carrier * * @param \Magento\Framework\DataObject|null $params + * * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -1552,6 +1557,7 @@ public function getDeliveryConfirmationTypes(\Magento\Framework\DataObject $para /** * Recursive replace sensitive fields in debug data by the mask + * * @param string $data * @return string */ @@ -1569,6 +1575,7 @@ protected function filterDebugData($data) /** * Parse track details response from Fedex + * * @param \stdClass $trackInfo * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -1639,6 +1646,7 @@ private function processTrackingDetails(\stdClass $trackInfo) /** * Parse delivery datetime from tracking details + * * @param \stdClass $trackInfo * @return \Datetime|null */ @@ -1655,8 +1663,7 @@ private function getDeliveryDateTime(\stdClass $trackInfo) } /** - * Get delivery address details in string representation - * Return City, State, Country Code + * Get delivery address details in string representation Return City, State, Country Code * * @param \stdClass $address * @return \Magento\Framework\Phrase|string @@ -1718,6 +1725,7 @@ private function processTrackDetailsEvents(array $events) /** * Append error message to rate result instance + * * @param string $trackingValue * @param string $errorMessage */ @@ -1759,8 +1767,7 @@ private function parseDate($timestamp) } /** - * Defines payment type by request. - * Two values are available: RECIPIENT or SENDER. + * Defines payment type by request. Two values are available: RECIPIENT or SENDER. * * @param DataObject $request * @return string diff --git a/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl b/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl index 6ae04ccee0775..62795f07239a6 100644 --- a/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl +++ b/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl @@ -355,7 +355,7 @@ - Shows the specific combination of service options combined with the service type that produced this committment in the set returned to the caller. + Shows the specific combination of service options combined with the service type that produced this commitment in the set returned to the caller. @@ -473,7 +473,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -916,7 +916,7 @@ - For customers producing their own Ground labels, this field specifies which secondary barcode will be printed on the label; so that the primary barcode produced by FedEx has the corect SCNC. + For customers producing their own Ground labels, this field specifies which secondary barcode will be printed on the label; so that the primary barcode produced by FedEx has the correct SCNC. @@ -1006,7 +1006,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -1151,7 +1151,7 @@ - Data required to complete the Destionation Control Statement for US exports. + Data required to complete the Destination Control Statement for US exports. @@ -2278,7 +2278,7 @@ - For thermal printer lables this indicates the size of the label and the location of the doc tab if present. + For thermal printer labels this indicates the size of the label and the location of the doc tab if present. @@ -2780,7 +2780,7 @@ - The descriptive data for a person or company entitiy doing business with FedEx. + The descriptive data for a person or company entity doing business with FedEx. @@ -3069,7 +3069,7 @@ - Shows the specific combination of service options combined with the service type that produced this committment in the set returned to the caller. + Shows the specific combination of service options combined with the service type that produced this commitment in the set returned to the caller. @@ -3195,7 +3195,7 @@ - If requesting rates using the PackageDetails element (one package at a time) in the request, the rates for each package will be returned in this element. Currently total piece total weight rates are also retuned in this element. + If requesting rates using the PackageDetails element (one package at a time) in the request, the rates for each package will be returned in this element. Currently total piece total weight rates are also returned in this element. @@ -3416,7 +3416,7 @@ - Identifies the total weight of the shipment being conveyed to FedEx.This is only applicable to International shipments and should only be used on the first package of a mutiple piece shipment.This value contains 1 explicit decimal position + Identifies the total weight of the shipment being conveyed to FedEx.This is only applicable to International shipments and should only be used on the first package of a multiple piece shipment.This value contains 1 explicit decimal position @@ -3582,7 +3582,7 @@ - The type of return shipment that is being requested. At present the only type of retrun shipment that is supported is PRINT_RETURN_LABEL. With this option you can print a return label to insert into the box of an outbound shipment. This option can not be used to print an outbound label. + The type of return shipment that is being requested. At present the only type of return shipment that is supported is PRINT_RETURN_LABEL. With this option you can print a return label to insert into the box of an outbound shipment. This option can not be used to print an outbound label. @@ -3655,7 +3655,7 @@ - These values control the optional features of service that may be combined in a commitment/rate comparision transaction. + These values control the optional features of service that may be combined in a commitment/rate comparison transaction. @@ -4731,7 +4731,7 @@ - The calculated varibale handling charge plus the net charge. + The calculated variable handling charge plus the net charge. diff --git a/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl b/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl index efbf75076c2cb..17a6f74cc09b8 100644 --- a/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl +++ b/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl @@ -354,7 +354,7 @@ - Shows the specific combination of service options combined with the service type that produced this committment in the set returned to the caller. + Shows the specific combination of service options combined with the service type that produced this commitment in the set returned to the caller. @@ -472,7 +472,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment commitment more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -1006,7 +1006,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -1148,7 +1148,7 @@ - Data required to complete the Destionation Control Statement for US exports. + Data required to complete the Destination Control Statement for US exports. @@ -2313,7 +2313,7 @@ - For thermal printer lables this indicates the size of the label and the location of the doc tab if present. + For thermal printer labels this indicates the size of the label and the location of the doc tab if present. @@ -3099,7 +3099,7 @@ - Shows the specific combination of service options combined with the service type that produced this committment in the set returned to the caller. + Shows the specific combination of service options combined with the service type that produced this commitment in the set returned to the caller. @@ -3216,7 +3216,7 @@ - If requesting rates using the PackageDetails element (one package at a time) in the request, the rates for each package will be returned in this element. Currently total piece total weight rates are also retuned in this element. + If requesting rates using the PackageDetails element (one package at a time) in the request, the rates for each package will be returned in this element. Currently total piece total weight rates are also returned in this element. @@ -3622,7 +3622,7 @@ - The type of return shipment that is being requested. At present the only type of retrun shipment that is supported is PRINT_RETURN_LABEL. With this option you can print a return label to insert into the box of an outbound shipment. This option can not be used to print an outbound label. + The type of return shipment that is being requested. At present the only type of return shipment that is supported is PRINT_RETURN_LABEL. With this option you can print a return label to insert into the box of an outbound shipment. This option can not be used to print an outbound label. @@ -3687,7 +3687,7 @@ - These values control the optional features of service that may be combined in a commitment/rate comparision transaction. + These values control the optional features of service that may be combined in a commitment/rate comparison transaction. @@ -4611,7 +4611,7 @@ - The calculated varibale handling charge plus the net charge. + The calculated variable handling charge plus the net charge. diff --git a/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl b/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl index 6c43af98ef682..54bb57d490c76 100644 --- a/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl +++ b/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl @@ -498,7 +498,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -1515,12 +1515,12 @@ - Data required to complete the Destionation Control Statement for US exports. + Data required to complete the Destination Control Statement for US exports. - List of applicable Statment types. + List of applicable Statement types. @@ -2524,7 +2524,7 @@ - For thermal printer lables this indicates the size of the label and the location of the doc tab if present. + For thermal printer labels this indicates the size of the label and the location of the doc tab if present. @@ -5225,7 +5225,7 @@ - The calculated varibale handling charge plus the net charge. + The calculated variable handling charge plus the net charge. diff --git a/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl b/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl index 8c11a5f660351..d8dc0fdfed4ab 100644 --- a/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl +++ b/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl @@ -498,7 +498,7 @@ For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction. - If this shipment cotains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. + If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request. @@ -1515,12 +1515,12 @@ - Data required to complete the Destionation Control Statement for US exports. + Data required to complete the Destination Control Statement for US exports. - List of applicable Statment types. + List of applicable Statement types. @@ -2524,7 +2524,7 @@ - For thermal printer lables this indicates the size of the label and the location of the doc tab if present. + For thermal printer labels this indicates the size of the label and the location of the doc tab if present. @@ -5225,7 +5225,7 @@ - The calculated varibale handling charge plus the net charge. + The calculated variable handling charge plus the net charge. diff --git a/app/code/Magento/GiftMessage/Test/Mftf/Data/GiftOptionsData.xml b/app/code/Magento/GiftMessage/Test/Mftf/Data/GiftOptionsData.xml index 951bbd3b787b7..bb3f566f1cbc6 100644 --- a/app/code/Magento/GiftMessage/Test/Mftf/Data/GiftOptionsData.xml +++ b/app/code/Magento/GiftMessage/Test/Mftf/Data/GiftOptionsData.xml @@ -7,23 +7,21 @@ --> - - 0 + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + + AllowGiftMessageForOrder - - 0 + + 1 - - - defaultWrappingAllowOrder - defaultAllowGiftReceipt - defaultAllowPrintedCard + + OrderDefault + ItemsDefault - - 1 + + 0 - - 1 + + 0 diff --git a/app/code/Magento/GiftMessage/Test/Mftf/Metadata/gift_options-meta.xml b/app/code/Magento/GiftMessage/Test/Mftf/Metadata/gift_options-meta.xml new file mode 100644 index 0000000000000..d4b7997e77d15 --- /dev/null +++ b/app/code/Magento/GiftMessage/Test/Mftf/Metadata/gift_options-meta.xml @@ -0,0 +1,24 @@ + + + + + + + + + string + + + string + + + + + + diff --git a/app/code/Magento/GroupedCatalogInventory/LICENSE.txt b/app/code/Magento/GroupedCatalogInventory/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/GroupedCatalogInventory/LICENSE_AFL.txt b/app/code/Magento/GroupedCatalogInventory/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/GroupedCatalogInventory/Plugin/OutOfStockFilter.php b/app/code/Magento/GroupedCatalogInventory/Plugin/OutOfStockFilter.php new file mode 100644 index 0000000000000..599f40deddde0 --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/Plugin/OutOfStockFilter.php @@ -0,0 +1,88 @@ +stockStatusRepository = $stockStatusRepository; + $this->criteriaInterfaceFactory = $criteriaInterfaceFactory; + } + + /** + * Removes out of stock products for requests that don't specify the super group + * + * @param Grouped $subject + * @param array|string $result + * @param \Magento\Framework\DataObject $buyRequest + * @return string|array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterPrepareForCartAdvanced( + Grouped $subject, + $result, + \Magento\Framework\DataObject $buyRequest + ) { + if (!is_array($result) && $result instanceof Product) { + $result = [$result]; + } + + // Only remove out-of-stock products if no quantities were specified + if (is_array($result) && !empty($result) && !$buyRequest->getData('super_group')) { + $productIds = []; + $productIdMap = []; + + foreach ($result as $index => $cartItem) { + $productIds[] = $cartItem->getId(); + $productIdMap[$cartItem->getId()] = $index; + } + + $criteria = $this->criteriaInterfaceFactory->create(); + $criteria->setProductsFilter($productIds); + + $stockStatusCollection = $this->stockStatusRepository->getList($criteria); + foreach ($stockStatusCollection->getItems() as $status) { + /** @var $status StockStatusInterface */ + if ($status->getStockStatus() == StockStatusInterface::STATUS_OUT_OF_STOCK) { + unset($result[$productIdMap[$status->getProductId()]]); + } + } + + unset($productIdMap); + } + + return $result; + } +} diff --git a/app/code/Magento/GroupedCatalogInventory/README.md b/app/code/Magento/GroupedCatalogInventory/README.md new file mode 100644 index 0000000000000..c229c7e9919ad --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/README.md @@ -0,0 +1 @@ +Magento_GroupedCatalogInventory contains behavior related to the inventory status of items within grouped products. diff --git a/app/code/Magento/GroupedCatalogInventory/Test/Unit/Plugin/OutOfStockFilterTest.php b/app/code/Magento/GroupedCatalogInventory/Test/Unit/Plugin/OutOfStockFilterTest.php new file mode 100644 index 0000000000000..07877938e36ae --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/Test/Unit/Plugin/OutOfStockFilterTest.php @@ -0,0 +1,221 @@ +searchCriteriaMock->expects($this->never())->method('setProductsFilter'); + $this->stockStatusRepositoryMock->expects($this->never())->method('getList'); + + $plugin = $this->getPluginInstance(); + + $result = $plugin->afterPrepareForCartAdvanced( + $this->subjectMock, + $nonArrayResult, + new DataObject() + ); + + $this->assertSame($nonArrayResult, $result); + } + + public function testFilterIgnoresResultIfSuperGroupIsPresent() + { + $this->searchCriteriaMock->expects($this->never())->method('setProductsFilter'); + $this->stockStatusRepositoryMock->expects($this->never())->method('getList'); + + $plugin = $this->getPluginInstance(); + + $product = $this->createProductMock(); + + $result = $plugin->afterPrepareForCartAdvanced( + $this->subjectMock, + [$product], + new DataObject(['super_group' => [123 => '1']]) + ); + + $this->assertSame([$product], $result); + } + + /** + * @param $originalResult + * @param $stockStatusCollection + * @param $expectedResult + * @dataProvider outOfStockProductData + */ + public function testFilterRemovesOutOfStockProductsWhenSuperGroupIsNotPresent( + $originalResult, + $stockStatusCollection, + $expectedResult + ) { + $this->stockStatusRepositoryMock + ->expects($this->once()) + ->method('getList') + ->with($this->searchCriteriaMock) + ->willReturn($stockStatusCollection); + + $plugin = $this->getPluginInstance(); + + $result = $plugin->afterPrepareForCartAdvanced( + $this->subjectMock, + $originalResult, + new DataObject() + ); + + $this->assertSame($expectedResult, $result); + } + + public function outOfStockProductData() + { + $product1 = $this->createProductMock(); + $product1->method('getId')->willReturn(123); + + $product2 = $this->createProductMock(); + $product2->method('getId')->willReturn(321); + + return [ + [[$product1, $product2], $this->createStatusResult([123 => false, 321 => true]), [1 => $product2]], + [[$product1], $this->createStatusResult([123 => true]), [0 => $product1]], + [$product1, $this->createStatusResult([123 => true]), [0 => $product1]] + ]; + } + + public function nonArrayResults() + { + return [ + [123], + ['abc'], + [new \stdClass()] + ]; + } + + protected function setUp() + { + $this->subjectMock = $this->getMockBuilder(Grouped::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->stockStatusRepositoryMock = $this->getMockBuilder(StockStatusRepositoryInterface::class) + ->getMock(); + + $this->searchCriteriaFactoryMock = $this->getMockBuilder(StockStatusCriteriaInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->searchCriteriaMock = $this->getMockBuilder(StockStatusCriteriaInterface::class) + ->getMock(); + + $this->stockStatusCollectionMock = $this->getMockBuilder(StockStatusCollectionInterface::class) + ->getMock(); + + $this->searchCriteriaFactoryMock + ->expects($this->any()) + ->method('create') + ->willReturn($this->searchCriteriaMock); + } + + private function createProductMock() + { + return $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return OutOfStockFilter + */ + private function getPluginInstance() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + /** @var OutOfStockFilter $filter */ + $filter = $objectManager->getObject(OutOfStockFilter::class, [ + 'stockStatusRepository' => $this->stockStatusRepositoryMock, + 'criteriaInterfaceFactory' => $this->searchCriteriaFactoryMock + ]); + + return $filter; + } + + private function createStatusResult(array $productStatuses) + { + $result = []; + + foreach ($productStatuses as $productId => $status) { + $mock = $this->getMockBuilder(StockStatusInterface::class) + ->getMock(); + + $mock->expects($this->any()) + ->method('getProductId') + ->willReturn($productId); + + $mock->expects($this->any()) + ->method('getStockStatus') + ->willReturn( + $status + ? StockStatusInterface::STATUS_IN_STOCK + : StockStatusInterface::STATUS_OUT_OF_STOCK + ); + + $result[] = $mock; + } + + $stockStatusCollection = $this->getMockBuilder(StockStatusCollectionInterface::class) + ->getMock(); + + $stockStatusCollection + ->expects($this->once()) + ->method('getItems') + ->willReturn($result); + + return $stockStatusCollection; + } +} diff --git a/app/code/Magento/GroupedCatalogInventory/composer.json b/app/code/Magento/GroupedCatalogInventory/composer.json new file mode 100644 index 0000000000000..c9946bc3f36ac --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-grouped-catalog-inventory", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-catalog": "*", + "magento/module-catalog-inventory": "*", + "magento/module-grouped-product": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\GroupedCatalogInventory\\": "" + } + } +} diff --git a/app/code/Magento/GroupedCatalogInventory/etc/di.xml b/app/code/Magento/GroupedCatalogInventory/etc/di.xml new file mode 100644 index 0000000000000..f38a9e845a159 --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/etc/di.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/app/code/Magento/GroupedCatalogInventory/etc/module.xml b/app/code/Magento/GroupedCatalogInventory/etc/module.xml new file mode 100644 index 0000000000000..246f10ff67934 --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/etc/module.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/app/code/Magento/GroupedCatalogInventory/registration.php b/app/code/Magento/GroupedCatalogInventory/registration.php new file mode 100644 index 0000000000000..8899a48edf6d5 --- /dev/null +++ b/app/code/Magento/GroupedCatalogInventory/registration.php @@ -0,0 +1,9 @@ + + + + + + + + + + <description value="Products in group should show in admin list even when they are out of stock"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93181"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="category1"/> + <createData entity="SimpleProduct" stepKey="simpleProduct1"> + <requiredEntity createDataKey="category1"/> + </createData> + <!--Out of Stock--> + <createData entity="SimpleProduct4" stepKey="simpleProduct2"> + <requiredEntity createDataKey="category1"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="category1" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="adminProductIndexPageAdd"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <actionGroup ref="fillGroupedProductForm" stepKey="fillMainProductForm"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <!-- Add two simple products to grouped product --> + <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToSection"/> + <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductSection"/> + <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup"/> + <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForFilter"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku1"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption1"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption2"/> + <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="addSelectedProducts"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + + <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToProducts"/> + + <waitForText userInput="$$simpleProduct1.name$$" stepKey="assertProductIsInTheList"/> + <waitForText userInput="$$simpleProduct2.name$$" stepKey="assertProduct2IsInTheList"/> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php index 4f4ed9de03e43..8721dd05a0fa6 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php @@ -244,7 +244,7 @@ protected function _getSelectHtmlWithValue(Attribute $attribute, $value) $options = $attribute->getSource()->getAllOptions(false); } if ($size = count($options)) { - // add empty vaue option + // add empty value option $firstOption = reset($options); if ('' === $firstOption['value']) { diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php index 8fd73e466083a..68b4d849099c1 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php @@ -113,6 +113,7 @@ protected function _prepareForm() 'class' => $behaviorCode, 'onchange' => 'varienImport.handleImportBehaviorSelector();', 'note' => ' ', + 'after_element_html' => $this->getImportBehaviorTooltip(), ] ); $fieldsets[$behaviorCode]->addField( @@ -252,4 +253,19 @@ protected function getDownloadSampleFileHtml() . '</a></span>'; return $html; } + + /** + * Get Import Behavior field tooltip + * + * @return string + */ + private function getImportBehaviorTooltip() + { + $html = '<div class="admin__field-tooltip tooltip"> + <a class="admin__field-tooltip-action action-help" target="_blank" title="What is this?" + href="https://docs.magento.com/m2/ce/user_guide/system/data-import.html"><span>' + . __('What is this?') + . '</span></a></div>'; + return $html; + } } diff --git a/app/code/Magento/ImportExport/Model/Source/Import/Behavior/Basic.php b/app/code/Magento/ImportExport/Model/Source/Import/Behavior/Basic.php index f28aae9f8ae1c..e0002474e1917 100644 --- a/app/code/Magento/ImportExport/Model/Source/Import/Behavior/Basic.php +++ b/app/code/Magento/ImportExport/Model/Source/Import/Behavior/Basic.php @@ -5,6 +5,8 @@ */ namespace Magento\ImportExport\Model\Source\Import\Behavior; +use Magento\ImportExport\Model\Import; + /** * Import behavior source model used for defining the behaviour during the import. * @@ -14,19 +16,19 @@ class Basic extends \Magento\ImportExport\Model\Source\Import\AbstractBehavior { /** - * {@inheritdoc} + * @inheritdoc */ public function toArray() { return [ - \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND => __('Add/Update'), - \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE => __('Replace'), - \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE => __('Delete') + Import::BEHAVIOR_APPEND => __('Add/Update'), + Import::BEHAVIOR_REPLACE => __('Replace'), + Import::BEHAVIOR_DELETE => __('Delete') ]; } /** - * {@inheritdoc} + * @inheritdoc */ public function getCode() { @@ -34,12 +36,23 @@ public function getCode() } /** - * {@inheritdoc} + * @inheritdoc */ public function getNotes($entityCode) { $messages = ['catalog_product' => [ - \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE => __("Note: Product IDs will be regenerated.") + Import::BEHAVIOR_APPEND => __( + "New product data is added to the existing product data for the existing entries in the database. " + . "All fields except sku can be updated." + ), + Import::BEHAVIOR_REPLACE => __( + "The existing product data is replaced with new data. <b>Exercise caution when replacing data " + . "because the existing product data will be completely cleared and all references " + . "in the system will be lost.</b>" + ), + Import::BEHAVIOR_DELETE => __( + "Any entities in the import data that already exist in the database are deleted from the database." + ), ]]; return isset($messages[$entityCode]) ? $messages[$entityCode] : []; } diff --git a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml new file mode 100644 index 0000000000000..87807eb9b0e85 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminImportIndexPage" url="admin/import/" area="admin" module="Magento_ImportExport"> + <section name="AdminImportHeaderSection"/> + <section name="AdminImportMainSection"/> + </page> +</pages> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml new file mode 100644 index 0000000000000..748580be09406 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminImportHeaderSection"> + <element name="checkDataButton" type="button" selector="#upload_button" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml new file mode 100644 index 0000000000000..2ce6b1e35777f --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminImportMainSection"> + <element name="entityType" type="select" selector="#entity"/> + <element name="importBehavior" type="select" selector="#basic_behavior"/> + <element name="selectFileToImport" type="input" selector="#import_file"/> + <element name="importButton" type="button" selector="#import_validation_container button" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml new file mode 100644 index 0000000000000..4cbb0603d9073 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportProductsWithDeleteBehaviorTest"> + <annotations> + <description value="Verify Magento native import products with delete behavior."/> + <stories value="Verify Magento native import products with delete behavior."/> + <features value="Import/Export"/> + <title value="Verify Magento native import products with delete behavior."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-30587"/> + <group value="importExport"/> + </annotations> + <before> + <!--Create Simple product--> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="name">Simple Product for Test</field> + <field key="sku">Simple Product for Test</field> + </createData> + <!--Create Virtual product--> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <field key="name">Virtual Product for Test</field> + <field key="sku">Virtual Product for Test</field> + </createData> + <!-- Create Downloadable product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"> + <field key="name">Api Downloadable Product for Test</field> + <field key="sku">Api Downloadable Product for Test</field> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="goToImportIndexPage"/> + <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> + <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehaviorElementVisible"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="Delete" stepKey="selectDeleteOption"/> + <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="catalog_products.csv" stepKey="attachFileForImport"/> + <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> + <click selector="{{AdminImportMainSection.importButton}}" stepKey="clickImportButton"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="Import successfully done" stepKey="assertSuccessMessage"/> + <see selector="{{AdminMessagesSection.notice}}" userInput="Created: 0, Updated: 0, Deleted: 3" stepKey="assertNotice"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchSimpleProductOnBackend"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchVirtualProductOnBackend"> + <argument name="product" value="$$createVirtualProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage1"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchDownloadableProductOnBackend"> + <argument name="product" value="$$createDownloadableProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage2"/> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php index a4c380d51ebe3..b81b9f9093d1f 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php @@ -191,7 +191,7 @@ public function testInitValidationStrategyExceed() } /** - * Test for method initValidationStrategy. Expected exeption due null incoming parameter + * Test for method initValidationStrategy. Expected exception due null incoming parameter */ public function testInitValidationStrategyException() { @@ -316,7 +316,7 @@ public function testAddTheSameErrorTwice() } /** - * Test for method getErrorsByCode. Expects receive errors with code, which present in incomming parameter. + * Test for method getErrorsByCode. Expects receive errors with code, which present in incoming parameter. */ public function testGetErrorsByCodeInArray() { @@ -328,7 +328,7 @@ public function testGetErrorsByCodeInArray() } /** - * Test for method getErrorsByCode. Unexpects receive errors with code, which present in incomming parameter. + * Test for method getErrorsByCode. Unexpects receive errors with code, which present in incoming parameter. */ public function testGetErrorsByCodeNotInArray() { diff --git a/app/code/Magento/ImportExport/i18n/en_US.csv b/app/code/Magento/ImportExport/i18n/en_US.csv index 60c9ee4c5a960..cae4d6e19868d 100644 --- a/app/code/Magento/ImportExport/i18n/en_US.csv +++ b/app/code/Magento/ImportExport/i18n/en_US.csv @@ -119,3 +119,6 @@ User,User "Error File","Error File" "Execution Time","Execution Time" Summary,Summary +"New product data is added to existing product data entries in the database. All fields except SKU can be updated.","New product data is added to existing product data entries in the database. All fields except SKU can be updated." +"All existing product data is replaced with the imported new data. <b>Exercise caution when replacing data. All existing product data will be completely cleared and all references in the system will be lost.</b>","All existing product data is replaced with the imported new data. <b>Exercise caution when replacing data. All existing product data will be completely cleared and all references in the system will be lost.</b>" +"Any entities in the import data that match existing entities in the database are deleted from the database.","Any entities in the import data that match existing entities in the database are deleted from the database." diff --git a/app/code/Magento/Indexer/Model/Message/Invalid.php b/app/code/Magento/Indexer/Model/Message/Invalid.php index 5a3f879b0ad80..79f9fcef9641e 100644 --- a/app/code/Magento/Indexer/Model/Message/Invalid.php +++ b/app/code/Magento/Indexer/Model/Message/Invalid.php @@ -6,6 +6,9 @@ namespace Magento\Indexer\Model\Message; +/** + * Message about invalid indexers. + */ class Invalid implements \Magento\Framework\Notification\MessageInterface { /** @@ -71,7 +74,7 @@ public function getText() return __( 'One or more <a href="%1">indexers are invalid</a>. Make sure your <a href="%2" target="_blank">Magento cron job</a> is running.', $url, - 'http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' + 'https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' ); //@codingStandardsIgnoreEnd } diff --git a/app/code/Magento/InstantPurchase/PaymentMethodIntegration/IntegrationFactory.php b/app/code/Magento/InstantPurchase/PaymentMethodIntegration/IntegrationFactory.php index cdca81ed98b7a..0b698afea1de3 100644 --- a/app/code/Magento/InstantPurchase/PaymentMethodIntegration/IntegrationFactory.php +++ b/app/code/Magento/InstantPurchase/PaymentMethodIntegration/IntegrationFactory.php @@ -65,13 +65,13 @@ public function create(VaultPaymentInterface $paymentMethod, int $storeId): Inte /** * Reads value from config. * - * @param $config + * @param array $config * @param string $field * @param string $default * @return string */ private function extractFromConfig($config, string $field, string $default): string { - return isset($config[$field]) ? $config[$field] : $default; + return $config[$field] ?? $default; } } diff --git a/app/code/Magento/InstantPurchase/README.md b/app/code/Magento/InstantPurchase/README.md index 534696bf353fc..9b618eaca997d 100644 --- a/app/code/Magento/InstantPurchase/README.md +++ b/app/code/Magento/InstantPurchase/README.md @@ -10,7 +10,7 @@ Prerequisites to display the Instant Purchase button: ## Structure -In addition to [a typical file structure for a Magento 2 module](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) `PaymentMethodsIntegration` directory contains interfaces and basic implementation of integration vault payment method to the instant purchase. +In addition to [a typical file structure for a Magento 2 module](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) `PaymentMethodsIntegration` directory contains interfaces and basic implementation of integration vault payment method to the instant purchase. ## Extensibility @@ -22,7 +22,7 @@ All payments created for instant purchase also have `'instant-purchase' => true` ### Payment method integration -Instant purchase support may be implemented for any payment method with [vault support](http://devdocs.magento.com/guides/v2.1/payments-integrations/vault/vault-intro.html). +Instant purchase support may be implemented for any payment method with [vault support](https://devdocs.magento.com/guides/v2.1/payments-integrations/vault/vault-intro.html). Basic implementation provided in `Magento\InstantPurchase\PaymentMethodIntegration` should be enough in most cases. It is not enabled by default to avoid issues on production sites and authors of vault payment method should verify correct work for instant purchase manually. To enable basic implementation just add single option to configuration of payemnt method in `config.xml`: @@ -52,7 +52,7 @@ Basic implementation is a good start point but it's recommended to provide own i The `Magento_InstantPurchase` module does not introduce backward incompatible changes. -You can track [backward incompatible changes in patch releases](http://devdocs.magento.com/guides/v2.2/release-notes/changes/ce_changes.html). +You can track [backward incompatible changes in patch releases](https://devdocs.magento.com/guides/v2.2/release-notes/changes/ce_changes.html). *** diff --git a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js index 6921f645a2330..a2acc40c6b86e 100644 --- a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js +++ b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js @@ -187,7 +187,7 @@ define([ }, /** - * Function to check the location of the child popoup window. + * Function to check the location of the child popup window. * Once detected if the callback is successful, parent window will be reloaded */ fnCheckLocation: function () { diff --git a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml index de4637847456e..8d3f70c2806aa 100644 --- a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml +++ b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml @@ -20,7 +20,7 @@ </field> <field id="price_range_step" translate="label" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Default Price Navigation Step</label> - <validate>validate-number validate-number-range number-range-0.01-1000000000</validate> + <validate>validate-number validate-number-range number-range-0.01-9999999999999999</validate> <depends> <field id="price_range_calculation">manual</field> </depends> diff --git a/app/code/Magento/Msrp/view/base/layout/cms_index_index.xml b/app/code/Magento/Msrp/view/base/layout/cms_index_index.xml new file mode 100644 index 0000000000000..b60bc71e9e70d --- /dev/null +++ b/app/code/Magento/Msrp/view/base/layout/cms_index_index.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <update handle="msrp_popup"/> + <body/> +</page> \ No newline at end of file diff --git a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml index 869d81563645a..dd5abd433073d 100644 --- a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml +++ b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml @@ -72,6 +72,8 @@ if ($product->isSaleable()) { } else { $data['addToCart']['addToCartButton'] = sprintf( 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]', + (int) $productId) . ',' . + sprintf('.block.widget .price-box[data-product-id=%s]+.product-item-actions button.tocart', (int) $productId ); } diff --git a/app/code/Magento/Msrp/view/frontend/templates/popup.phtml b/app/code/Magento/Msrp/view/frontend/templates/popup.phtml index 7ccd606b8f113..e0b3dd77dedcb 100644 --- a/app/code/Magento/Msrp/view/frontend/templates/popup.phtml +++ b/app/code/Magento/Msrp/view/frontend/templates/popup.phtml @@ -30,12 +30,11 @@ <span id="map-popup-price" class="actual-price"></span> </div> </div> - <form action="" method="POST" id="product_addtocart_form_from_popup" class="map-form-addtocart"> - <input type="hidden" name="product" class="product_id" value="" id="map-popup-product-id"/> + <form action="" method="POST" class="map-form-addtocart"> + <input type="hidden" name="product" class="product_id" value="" /> <button type="button" title="<?= $block->escapeHtml(__('Add to Cart')) ?>" - class="action tocart primary" - id="map-popup-button"> + class="action tocart primary"> <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> </button> <div class="additional-addtocart-box"> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Page/NewMultishippingAddressPage.xml b/app/code/Magento/Multishipping/Test/Mftf/Page/NewMultishippingAddressPage.xml new file mode 100644 index 0000000000000..f25687a012ec5 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Page/NewMultishippingAddressPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="NewMultishippingAddressPage" url="/multishipping/checkout_address/newShipping" module="Multishipping" area="storefront"> + </page> +</pages> diff --git a/app/code/Magento/MysqlMq/Model/QueueManagement.php b/app/code/Magento/MysqlMq/Model/QueueManagement.php index 0840add2a5c42..74d2154a36b0a 100644 --- a/app/code/Magento/MysqlMq/Model/QueueManagement.php +++ b/app/code/Magento/MysqlMq/Model/QueueManagement.php @@ -107,9 +107,10 @@ public function addMessagesToQueues($topic, $messages, $queueNames) } /** - * Mark messages to be deleted if sufficient amount of time passed since last update * Delete marked messages * + * Mark messages to be deleted if sufficient amount of time passed since last update + * * @return void */ public function markMessagesForDelete() @@ -223,7 +224,7 @@ private function getErrorMessageLifetime() } /** - * In progress message delay befor retry + * In progress message delay before retry * * Indicates how long message will stay in IN PROGRESS status before attempted to retry * diff --git a/app/code/Magento/NewRelicReporting/etc/db_schema.xml b/app/code/Magento/NewRelicReporting/etc/db_schema.xml index b5db533f90c75..c6e61b88f4b1b 100644 --- a/app/code/Magento/NewRelicReporting/etc/db_schema.xml +++ b/app/code/Magento/NewRelicReporting/etc/db_schema.xml @@ -38,8 +38,8 @@ comment="Entity ID"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer ID"/> - <column xsi:type="decimal" name="total" scale="0" precision="10" unsigned="true" nullable="true"/> - <column xsi:type="decimal" name="total_base" scale="0" precision="10" unsigned="true" nullable="true"/> + <column xsi:type="decimal" name="total" scale="4" precision="20" unsigned="true" nullable="true"/> + <column xsi:type="decimal" name="total_base" scale="4" precision="20" unsigned="true" nullable="true"/> <column xsi:type="int" name="item_count" padding="10" unsigned="true" nullable="false" identity="false" comment="Line Item Count"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php b/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php index 189549d2a73d2..52009dad6614b 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php @@ -141,8 +141,7 @@ public function addSubscribersInfo() } /** - * Checks if field is 'subscribers_total', 'subscribers_sent' - * to add specific filter or adds regular filter + * Checks if field is 'subscribers_total', 'subscribers_sent' to add specific filter or adds regular filter * * @param string $field * @param null|string|array $condition @@ -210,7 +209,7 @@ public function addSubscriberFilter($subscriberId) } /** - * Add filter by only ready fot sending item + * Add filter by only ready for sending item * * @return $this */ diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 50e6bb7e878c0..e7e5d5f202811 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -591,7 +591,12 @@ protected function _updateCustomerSubscription($customerId, $subscribe) if (AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED == $this->customerAccountManagement->getConfirmationStatus($customerId) ) { - $status = self::STATUS_UNCONFIRMED; + if ($this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED) { + // if a customer was already subscribed then keep the subscribed + $status = self::STATUS_SUBSCRIBED; + } else { + $status = self::STATUS_UNCONFIRMED; + } } elseif ($isConfirmNeed) { if ($this->getStatus() != self::STATUS_SUBSCRIBED) { $status = self::STATUS_NOT_ACTIVE; diff --git a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php new file mode 100644 index 0000000000000..9860798b2b9f3 --- /dev/null +++ b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Newsletter\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Class PredispatchNewsletterObserver + */ +class PredispatchNewsletterObserver implements ObserverInterface +{ + /** + * Configuration path to newsletter active setting + */ + const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var UrlInterface + */ + private $url; + + /** + * PredispatchNewsletterObserver constructor. + * + * @param ScopeConfigInterface $scopeConfig + * @param UrlInterface $url + */ + public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url) + { + $this->scopeConfig = $scopeConfig; + $this->url = $url; + } + + /** + * Redirect newsletter routes to 404 when newsletter module is disabled. + * + * @param Observer $observer + */ + public function execute(Observer $observer) : void + { + if (!$this->scopeConfig->getValue( + self::XML_PATH_NEWSLETTER_ACTIVE, + ScopeInterface::SCOPE_STORE + ) + ) { + $defaultNoRouteUrl = $this->scopeConfig->getValue( + 'web/default/no_route', + ScopeInterface::SCOPE_STORE + ); + $redirectUrl = $this->url->getUrl($defaultNoRouteUrl); + $observer->getControllerAction() + ->getResponse() + ->setRedirect($redirectUrl); + } + } +} diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml index 92d17e9d71e2f..059f157c407c9 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml @@ -6,13 +6,10 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Create an Account. Check Sign Up for Newsletter checkbox --> <actionGroup name="StorefrontCreateNewAccountNewsletterChecked" extends="SignUpNewUserFromStorefrontActionGroup"> - <arguments> - <argument name="Customer"/> - </arguments> <click selector="{{StorefrontCustomerCreateFormSection.signUpForNewsletter}}" stepKey="selectSignUpForNewsletterCheckbox" after="fillLastName"/> <see stepKey="seeDescriptionNewsletter" userInput='You are subscribed to "General Subscription".' selector="{{CustomerMyAccountPage.DescriptionNewsletter}}" /> </actionGroup> @@ -28,4 +25,9 @@ <see stepKey="seeThankYouMessage" userInput="Thank you for registering with NewStore."/> </actionGroup> + <!--Check Subscribed Newsletter via StoreFront--> + <actionGroup name="CheckSubscribedNewsletterActionGroup"> + <amOnPage url="{{StorefrontNewsletterManagePage.url}}" stepKey="goToNewsletterManage"/> + <seeCheckboxIsChecked selector="{{StorefrontNewsletterManageSection.subscriptionCheckbox}}" stepKey="checkSubscribedNewsletter"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml new file mode 100644 index 0000000000000..81fd3eb7c391c --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontNewsletterManagePage" url="newsletter/manage/" area="storefront" module="Magento_Newsletter"> + <section name="StorefrontNewsletterManageSection"/> + </page> +</pages> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml new file mode 100644 index 0000000000000..96a944a4952ac --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontNewsletterManageSection"> + <element name="subscriptionCheckbox" type="checkbox" selector="#subscription" /> + </section> +</sections> diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php new file mode 100644 index 0000000000000..38d69e5128af1 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Newsletter\Test\Unit\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\Newsletter\Observer\PredispatchNewsletterObserver + */ +class PredispatchNewsletterObserverTest extends TestCase +{ + /** + * @var Observer|\PHPUnit_Framework_MockObject_MockObject + */ + private $mockObject; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlMock; + + /** + * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $redirectMock; + + /** + * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseMock; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlMock = $this->getMockBuilder(UrlInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseMock = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setRedirect']) + ->getMockForAbstractClass(); + $this->redirectMock = $this->getMockBuilder(RedirectInterface::class) + ->getMock(); + $this->objectManager = new ObjectManager($this); + $this->mockObject = $this->objectManager->getObject( + PredispatchNewsletterObserver::class, + [ + 'scopeConfig' => $this->configMock, + 'url' => $this->urlMock + ] + ); + } + + /** + * Test with enabled newsletter active config. + */ + public function testNewsletterEnabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getResponse', 'getData', 'setRedirect']) + ->getMockForAbstractClass(); + + $this->configMock->method('getValue') + ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(true); + $observerMock->expects($this->never()) + ->method('getData') + ->with('controller_action') + ->willReturnSelf(); + + $observerMock->expects($this->never()) + ->method('getResponse') + ->willReturnSelf(); + + $this->assertNull($this->mockObject->execute($observerMock)); + } + + /** + * Test with disabled newsletter active config. + */ + public function testNewsletterDisabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getControllerAction', 'getResponse']) + ->getMockForAbstractClass(); + + $this->configMock->expects($this->at(0)) + ->method('getValue') + ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(false); + + $expectedRedirectUrl = 'https://test.com/index'; + + $this->configMock->expects($this->at(1)) + ->method('getValue') + ->with('web/default/no_route', ScopeInterface::SCOPE_STORE) + ->willReturn($expectedRedirectUrl); + + $this->urlMock->expects($this->once()) + ->method('getUrl') + ->willReturn($expectedRedirectUrl); + + $observerMock->expects($this->once()) + ->method('getControllerAction') + ->willReturnSelf(); + + $observerMock->expects($this->once()) + ->method('getResponse') + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->once()) + ->method('setRedirect') + ->with($expectedRedirectUrl); + + $this->assertNull($this->mockObject->execute($observerMock)); + } +} diff --git a/app/code/Magento/Newsletter/etc/adminhtml/system.xml b/app/code/Magento/Newsletter/etc/adminhtml/system.xml index 277005240eabc..16af7b2158dde 100644 --- a/app/code/Magento/Newsletter/etc/adminhtml/system.xml +++ b/app/code/Magento/Newsletter/etc/adminhtml/system.xml @@ -11,6 +11,13 @@ <label>Newsletter</label> <tab>customer</tab> <resource>Magento_Newsletter::newsletter</resource> + <group id="general" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>General Options</label> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Enabled</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> <group id="subscription" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Subscription Options</label> <field id="allow_guest_subscribe" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/Newsletter/etc/config.xml b/app/code/Magento/Newsletter/etc/config.xml index f976ece8d712f..4c5e385105cf9 100644 --- a/app/code/Magento/Newsletter/etc/config.xml +++ b/app/code/Magento/Newsletter/etc/config.xml @@ -8,6 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <default> <newsletter> + <general> + <active>1</active> + </general> <subscription> <allow_guest_subscribe>1</allow_guest_subscribe> <confirm>0</confirm> diff --git a/app/code/Magento/Newsletter/etc/frontend/events.xml b/app/code/Magento/Newsletter/etc/frontend/events.xml new file mode 100644 index 0000000000000..6c46d562f5167 --- /dev/null +++ b/app/code/Magento/Newsletter/etc/frontend/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_newsletter"> + <observer name="newsletter_enabled" instance="Magento\Newsletter\Observer\PredispatchNewsletterObserver" /> + </event> +</config> diff --git a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml index 99190bb35fcf4..fd55fce8ee016 100644 --- a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml @@ -8,7 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-newsletter-subscriptions-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" ifconfig="newsletter/general/active" name="customer-account-navigation-newsletter-subscriptions-link"> <arguments> <argument name="path" xsi:type="string">newsletter/manage</argument> <argument name="label" xsi:type="string" translate="true">Newsletter Subscriptions</argument> diff --git a/app/code/Magento/Newsletter/view/frontend/layout/default.xml b/app/code/Magento/Newsletter/view/frontend/layout/default.xml index d84f4894a9d2e..32a08359333c9 100644 --- a/app/code/Magento/Newsletter/view/frontend/layout/default.xml +++ b/app/code/Magento/Newsletter/view/frontend/layout/default.xml @@ -8,10 +8,10 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="head.components"> - <block class="Magento\Framework\View\Element\Js\Components" name="newsletter_head_components" template="Magento_Newsletter::js/components.phtml"/> + <block class="Magento\Framework\View\Element\Js\Components" name="newsletter_head_components" template="Magento_Newsletter::js/components.phtml" ifconfig="newsletter/general/active"/> </referenceBlock> <referenceContainer name="footer"> - <block class="Magento\Newsletter\Block\Subscribe" name="form.subscribe" as="subscribe" before="-" template="Magento_Newsletter::subscribe.phtml"/> + <block class="Magento\Newsletter\Block\Subscribe" name="form.subscribe" as="subscribe" before="-" template="Magento_Newsletter::subscribe.phtml" ifconfig="newsletter/general/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index f6b7859af7c96..8068447e5ca99 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -86,7 +86,7 @@ sub vcl_recv { } elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") { set req.http.Accept-Encoding = "deflate"; } else { - # unkown algorithm + # unknown algorithm unset req.http.Accept-Encoding; } } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 388157af184a4..6c8414a5cb641 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -87,7 +87,7 @@ sub vcl_recv { } elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") { set req.http.Accept-Encoding = "deflate"; } else { - # unkown algorithm + # unknown algorithm unset req.http.Accept-Encoding; } } diff --git a/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php new file mode 100644 index 0000000000000..8afa064efd3ea --- /dev/null +++ b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Payment\Api\Data; + +use Magento\Framework\DataObject\KeyValueObjectInterface; + +/** + * Payment additional info interface. + */ +interface PaymentAdditionalInfoInterface extends KeyValueObjectInterface +{ +} diff --git a/app/code/Magento/Payment/Model/Method/AbstractMethod.php b/app/code/Magento/Payment/Model/Method/AbstractMethod.php index 33200014c7ec1..c0ccf887b18d7 100644 --- a/app/code/Magento/Payment/Model/Method/AbstractMethod.php +++ b/app/code/Magento/Payment/Model/Method/AbstractMethod.php @@ -24,7 +24,7 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @deprecated 100.0.6 * @see \Magento\Payment\Model\Method\Adapter - * @see http://devdocs.magento.com/guides/v2.1/payments-integrations/payment-gateway/payment-gateway-intro.html + * @see https://devdocs.magento.com/guides/v2.1/payments-integrations/payment-gateway/payment-gateway-intro.html * @since 100.0.2 */ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibleModel implements @@ -258,7 +258,7 @@ protected function initializeData($data = []) } /** - * {inheritdoc} + * @inheritdoc * @deprecated 100.2.0 */ public function setStore($storeId) @@ -267,7 +267,7 @@ public function setStore($storeId) } /** - * {inheritdoc} + * @inheritdoc * @deprecated 100.2.0 */ public function getStore() @@ -360,7 +360,8 @@ public function canRefundPartialPerInvoice() } /** - * Check void availability + * Check void availability. + * * @return bool * @internal param \Magento\Framework\DataObject $payment * @api @@ -372,8 +373,9 @@ public function canVoid() } /** - * Using internal pages for input payment data - * Can be used in admin + * Using internal pages for input payment data. + * + * Can be used in admin. * * @return bool * @deprecated 100.2.0 @@ -715,7 +717,8 @@ public function void(\Magento\Payment\Model\InfoInterface $payment) } /** - * Whether this method can accept or deny payment + * Whether this method can accept or deny payment. + * * @return bool * @api * @deprecated 100.2.0 @@ -867,8 +870,7 @@ public function isActive($storeId = null) } /** - * Method that will be executed instead of authorize or capture - * if flag isInitializeNeeded set to true + * Method that will be executed instead of authorize or capture if flag isInitializeNeeded set to true. * * @param string $paymentAction * @param object $stateObject @@ -884,8 +886,9 @@ public function initialize($paymentAction, $stateObject) } /** - * Get config payment action url - * Used to universalize payment actions when processing payment place + * Get config payment action url. + * + * Used to universalize payment actions when processing payment place. * * @return string * @api diff --git a/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php new file mode 100644 index 0000000000000..c4f135d5e0044 --- /dev/null +++ b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Payment\Model; + +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; + +/** + * Payment additional info class. + */ +class PaymentAdditionalInfo implements PaymentAdditionalInfoInterface +{ + /** + * @var string + */ + private $key; + + /** + * @var string + */ + private $value; + + /** + * @inheritdoc + */ + public function getKey() + { + return $this->key; + } + + /** + * @inheritdoc + */ + public function getValue() + { + return $this->value; + } + + /** + * @inheritdoc + */ + public function setKey($key) + { + $this->key = $key; + return $key; + } + + /** + * @inheritdoc + */ + public function setValue($value) + { + $this->value = $value; + return $value; + } +} diff --git a/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php b/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php index 63ca1b47dc08c..75916dd2ea99b 100644 --- a/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php +++ b/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php @@ -160,7 +160,7 @@ public function testSalesOrderBeforeSaveSetForced() * The method should check that the payment is available, as this is not always the case. * * @expectedException \Magento\Framework\Exception\LocalizedException - * @exceptedExceptionMessage Please provide payment for the order. + * @expectedExceptionMessage Please provide payment for the order. */ public function testDoesNothingWhenNoPaymentIsAvailable() { diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml index 74f553cc64094..b7422bb00d543 100644 --- a/app/code/Magento/Payment/etc/di.xml +++ b/app/code/Magento/Payment/etc/di.xml @@ -7,6 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Payment\Api\Data\PaymentMethodInterface" type="Magento\Payment\Model\PaymentMethod"/> + <preference for="Magento\Payment\Api\Data\PaymentAdditionalInfoInterface" type="Magento\Payment\Model\PaymentAdditionalInfo"/> <preference for="Magento\Payment\Api\PaymentMethodListInterface" type="Magento\Payment\Model\PaymentMethodList"/> <preference for="Magento\Payment\Gateway\Validator\ResultInterface" type="Magento\Payment\Gateway\Validator\Result"/> <preference for="Magento\Payment\Gateway\ConfigFactoryInterface" type="Magento\Payment\Gateway\Config\ConfigFactory" /> diff --git a/app/code/Magento/Payment/view/adminhtml/web/transparent.js b/app/code/Magento/Payment/view/adminhtml/web/js/transparent.js similarity index 100% rename from app/code/Magento/Payment/view/adminhtml/web/transparent.js rename to app/code/Magento/Payment/view/adminhtml/web/js/transparent.js diff --git a/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js index c22121622d264..7651fba1090c1 100644 --- a/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js +++ b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js @@ -21,7 +21,7 @@ define([], function () { /** * CVV number validation. - * Validate digit count fot CVV code. + * Validate digit count for CVV code. * * @param {*} value * @param {Number} maxLength diff --git a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php index c2056aea08c00..5d5db0128b1eb 100644 --- a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php +++ b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php @@ -5,7 +5,6 @@ */ namespace Magento\Paypal\Model\Config\Structure\Element; -use Magento\Framework\App\RequestInterface; use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructure; use Magento\Paypal\Model\Config\StructurePlugin as ConfigStructurePlugin; @@ -14,19 +13,6 @@ */ class FieldPlugin { - /** - * @var RequestInterface - */ - private $request; - - /** - * @param RequestInterface $request - */ - public function __construct(RequestInterface $request) - { - $this->request = $request; - } - /** * Get original configPath (not changed by PayPal configuration inheritance) * @@ -36,7 +22,7 @@ public function __construct(RequestInterface $request) */ public function afterGetConfigPath(FieldConfigStructure $subject, $result) { - if (!$result && $this->request->getParam('section') == 'payment') { + if (!$result && strpos($subject->getPath(), 'payment_') === 0) { $result = preg_replace( '@^(' . implode('|', ConfigStructurePlugin::getPaypalConfigCountries(true)) . ')/@', 'payment/', diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index 33fe5ec33002b..e52a85da3e829 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -44,7 +44,7 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod * * @var bool */ - protected $_isGateway = false; + protected $_isGateway = true; /** * Availability option diff --git a/app/code/Magento/Paypal/Model/Report/Settlement.php b/app/code/Magento/Paypal/Model/Report/Settlement.php index 4fdc4cf8f7afd..462ca2f979420 100644 --- a/app/code/Magento/Paypal/Model/Report/Settlement.php +++ b/app/code/Magento/Paypal/Model/Report/Settlement.php @@ -451,7 +451,7 @@ private function formatAmountColumn($lineItem) } /** - * Load report by unique key (accoutn + report date) + * Load report by unique key (account + report date) * * @return $this */ diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php index 8615b91383aaa..f0dda20b71c76 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php @@ -7,7 +7,6 @@ use Magento\Paypal\Model\Config\Structure\Element\FieldPlugin as FieldConfigStructurePlugin; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Framework\App\RequestInterface; use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructureMock; class FieldPluginTest extends \PHPUnit\Framework\TestCase @@ -22,11 +21,6 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase */ private $objectManagerHelper; - /** - * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $requestMock; - /** * @var FieldConfigStructureMock|\PHPUnit_Framework_MockObject_MockObject */ @@ -34,16 +28,13 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->requestMock = $this->getMockBuilder(RequestInterface::class) - ->getMockForAbstractClass(); $this->subjectMock = $this->getMockBuilder(FieldConfigStructureMock::class) ->disableOriginalConstructor() ->getMock(); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->plugin = $this->objectManagerHelper->getObject( - FieldConfigStructurePlugin::class, - ['request' => $this->requestMock] + FieldConfigStructurePlugin::class ); } @@ -56,10 +47,9 @@ public function testAroundGetConfigPathHasResult() public function testAroundGetConfigPathNonPaymentSection() { - $this->requestMock->expects(static::once()) - ->method('getParam') - ->with('section') - ->willReturn('non-payment'); + $this->subjectMock->expects($this->once()) + ->method('getPath') + ->willReturn('non-payment/group/field'); $this->assertNull($this->plugin->afterGetConfigPath($this->subjectMock, null)); } @@ -72,11 +62,7 @@ public function testAroundGetConfigPathNonPaymentSection() */ public function testAroundGetConfigPath($subjectPath, $expectedConfigPath) { - $this->requestMock->expects(static::once()) - ->method('getParam') - ->with('section') - ->willReturn('payment'); - $this->subjectMock->expects(static::once()) + $this->subjectMock->expects($this->exactly(2)) ->method('getPath') ->willReturn($subjectPath); diff --git a/app/code/Magento/Paypal/etc/config.xml b/app/code/Magento/Paypal/etc/config.xml index f0df648af9072..5fc457fb4443d 100644 --- a/app/code/Magento/Paypal/etc/config.xml +++ b/app/code/Magento/Paypal/etc/config.xml @@ -100,6 +100,7 @@ <instant_purchase> <tokenFormat>\Magento\Paypal\Model\InstantPurchase\Payflow\Pro\TokenFormatter</tokenFormat> </instant_purchase> + <group>paypal</group> </payflowpro_cc_vault> <paypal_billing_agreement> <active>1</active> diff --git a/app/code/Magento/Persistent/Block/Header/Additional.php b/app/code/Magento/Persistent/Block/Header/Additional.php index c740f5a3469fb..dfde2adf1e6ab 100644 --- a/app/code/Magento/Persistent/Block/Header/Additional.php +++ b/app/code/Magento/Persistent/Block/Header/Additional.php @@ -5,6 +5,10 @@ */ namespace Magento\Persistent\Block\Header; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Persistent\Helper\Data; + /** * Remember Me block * @@ -30,20 +34,37 @@ class Additional extends \Magento\Framework\View\Element\Html\Link protected $customerRepository; /** - * Constructor - * + * @var string + */ + protected $_template = 'Magento_Persistent::additional.phtml'; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var Data + */ + private $persistentHelper; + + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Customer\Helper\View $customerViewHelper * @param \Magento\Persistent\Helper\Session $persistentSessionHelper * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository * @param array $data + * @param Json|null $jsonSerializer + * @param Data|null $persistentHelper */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Customer\Helper\View $customerViewHelper, \Magento\Persistent\Helper\Session $persistentSessionHelper, \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, - array $data = [] + array $data = [], + Json $jsonSerializer = null, + Data $persistentHelper = null ) { $this->isScopePrivate = true; $this->_customerViewHelper = $customerViewHelper; @@ -51,6 +72,8 @@ public function __construct( $this->customerRepository = $customerRepository; parent::__construct($context, $data); $this->_isScopePrivate = true; + $this->jsonSerializer = $jsonSerializer ?: ObjectManager::getInstance()->get(Json::class); + $this->persistentHelper = $persistentHelper ?: ObjectManager::getInstance()->get(Data::class); } /** @@ -64,17 +87,26 @@ public function getHref() } /** - * Render additional header html + * Get customer id. * - * @return string + * @return int */ - protected function _toHtml() + public function getCustomerId(): int { - if ($this->_persistentSessionHelper->getSession()->getCustomerId()) { - return '<span><a ' . $this->getLinkAttributes() . ' >' . __('Not you?') - . '</a></span>'; - } + return $this->_persistentSessionHelper->getSession()->getCustomerId(); + } - return ''; + /** + * Get persistent config. + * + * @return string + */ + public function getConfig(): string + { + return $this->jsonSerializer->serialize( + [ + 'expirationLifetime' => $this->persistentHelper->getLifeTime(), + ] + ); } } diff --git a/app/code/Magento/Persistent/CustomerData/Persistent.php b/app/code/Magento/Persistent/CustomerData/Persistent.php new file mode 100644 index 0000000000000..5800e4e7aeeb5 --- /dev/null +++ b/app/code/Magento/Persistent/CustomerData/Persistent.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\CustomerData; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Customer\Helper\View; +use Magento\Persistent\Helper\Session; + +/** + * Customer persistent section + */ +class Persistent implements SectionSourceInterface +{ + /** + * @var Session + */ + private $persistentSession; + + /** + * @var View + */ + private $customerViewHelper; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @param Session $persistentSession + * @param View $customerViewHelper + * @param CustomerRepositoryInterface $customerRepository + */ + public function __construct( + Session $persistentSession, + View $customerViewHelper, + CustomerRepositoryInterface $customerRepository + ) { + $this->persistentSession = $persistentSession; + $this->customerViewHelper = $customerViewHelper; + $this->customerRepository = $customerRepository; + } + + /** + * Get data. + * + * @return array + */ + public function getSectionData(): array + { + if (!$this->persistentSession->isPersistent()) { + return []; + } + + $customerId = $this->persistentSession->getSession()->getCustomerId(); + if (!$customerId) { + return []; + } + + $customer = $this->customerRepository->getById($customerId); + + return [ + 'fullname' => $this->customerViewHelper->getCustomerName($customer), + ]; + } +} diff --git a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php index 2c0259bd12b00..2641102ca4d72 100644 --- a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php +++ b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php @@ -108,8 +108,8 @@ public function beforeSavePaymentInformationAndPlaceOrder( $this->customerSession->setCustomerId(null); $this->customerSession->setCustomerGroupId(null); $this->quoteManager->convertCustomerCartToGuest(); - /** @var \Magento\Quote\Api\Data\CartInterface $quote */ - $quote = $this->cartRepository->get($this->checkoutSession->getQuote()->getId()); + $quoteId = $this->checkoutSession->getQuoteId(); + $quote = $this->cartRepository->get($quoteId); $quote->setCustomerEmail($email); $quote->getAddressesCollection()->walk('setEmail', ['email' => $email]); $this->cartRepository->save($quote); diff --git a/app/code/Magento/Persistent/Model/Observer.php b/app/code/Magento/Persistent/Model/Observer.php index 53fe5f95531e1..81c2870071a2e 100644 --- a/app/code/Magento/Persistent/Model/Observer.php +++ b/app/code/Magento/Persistent/Model/Observer.php @@ -86,13 +86,8 @@ public function __construct( */ public function emulateWelcomeBlock($block) { - $customerName = $this->_customerViewHelper->getCustomerName( - $this->customerRepository->getById($this->_persistentSession->getSession()->getCustomerId()) - ); + $block->setWelcome(' '); - $this->_applyAccountLinksPersistentData(); - $welcomeMessage = __('Welcome, %1!', $customerName); - $block->setWelcome($welcomeMessage); return $this; } diff --git a/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php b/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php new file mode 100644 index 0000000000000..be8998bc9be14 --- /dev/null +++ b/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\Model\Plugin; + +/** + * Plugin for Magento\Framework\App\Http\Context to create new page cache variation for persistent session. + */ +class PersistentCustomerContext +{ + /** + * Persistent session. + * + * @var \Magento\Persistent\Helper\Session + */ + private $persistentSession; + + /** + * @param \Magento\Persistent\Helper\Session $persistentSession + */ + public function __construct( + \Magento\Persistent\Helper\Session $persistentSession + ) { + $this->persistentSession = $persistentSession; + } + + /** + * Sets appropriate header if customer session is persistent. + * + * @param \Magento\Framework\App\Http\Context $subject + * @return mixed + */ + public function beforeGetVaryString(\Magento\Framework\App\Http\Context $subject) + { + if ($this->persistentSession->isPersistent()) { + $subject->setValue('PERSISTENT', 1, 0); + } + } +} diff --git a/app/code/Magento/Persistent/Model/QuoteManager.php b/app/code/Magento/Persistent/Model/QuoteManager.php index cd7ce400a0be1..35c2c70be30dc 100644 --- a/app/code/Magento/Persistent/Model/QuoteManager.php +++ b/app/code/Magento/Persistent/Model/QuoteManager.php @@ -108,8 +108,9 @@ public function setGuest($checkQuote = false) */ public function convertCustomerCartToGuest() { + $quoteId = $this->checkoutSession->getQuoteId(); /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->quoteRepository->get($this->checkoutSession->getQuoteId()); + $quote = $this->quoteRepository->get($quoteId); if ($quote && $quote->getId()) { $this->_setQuotePersistent = false; $quote->setIsActive(true) diff --git a/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml new file mode 100644 index 0000000000000..293fa04d80462 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CustomerLoginOnStorefrontWithRememberMeChecked" extends="LoginToStorefrontActionGroup"> + <checkOption selector="{{StorefrontCustomerSignInFormSection.rememberMe}}" + before="clickSignInAccountButton" + stepKey="checkRememberMe"/> + </actionGroup> + + <actionGroup name="CustomerLoginOnStorefrontWithRememberMeUnChecked" extends="LoginToStorefrontActionGroup"> + <uncheckOption selector="{{StorefrontCustomerSignInFormSection.rememberMe}}" + before="clickSignInAccountButton" + stepKey="unCheckRememberMe"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml new file mode 100644 index 0000000000000..c2220c33a6052 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSignInFormSection"> + <element name="rememberMe" type="checkbox" selector="[name='persistent_remember_me']"/> + </section> +</sections> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml new file mode 100644 index 0000000000000..2b58e5c7bf62b --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest"> + <annotations> + <features value="Persistent"/> + <stories value="MAGETWO-97278 - Incorrect use of cookies for customer"/> + <title value="Checking welcome message for persistent customer after logout"/> + <description value="Checking welcome message for persistent customer after logout"/> + <severity value="MAJOR"/> + <testCaseId value="MC-10800"/> + <group value="persistent"/> + <group value="customer"/> + </annotations> + <before> + <!--Enable Persistence--> + <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> + <createData entity="PersistentLogoutClearDisable" stepKey="persistentLogoutClearDisable"/> + + <!--Create customers--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomerForPersistent"> + <field key="firstname">John1</field> + <field key="lastname">Doe1</field> + </createData> + </before> + <after> + <!--Roll back configuration--> + <createData entity="PersistentConfigDefault" stepKey="setDefaultPersistentState"/> + <createData entity="PersistentLogoutClearEnabled" stepKey="persistentLogoutClearEnabled"/> + + <!-- Logout customer on Storefront--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> + <!--Delete customers--> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomerForPersistent" stepKey="deleteCustomerForPersistent"/> + </after> + <!--Login as a Customer with remember me unchecked--> + <actionGroup ref="CustomerLoginOnStorefrontWithRememberMeUnChecked" stepKey="loginToStorefrontAccountWithRememberMeUnchecked"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Check customer name and last name in welcome message--> + <seeInCurrentUrl url="{{StorefrontCustomerDashboardPage.url}}" stepKey="seeCustomerAccountPageUrl"/> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeLoggedInCustomerWelcomeMessage"/> + <!--Logout and check default welcome message--> + <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout"/> + <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" wait="5" stepKey="seeCustomerSignOutPageUrl"/> + <see userInput="Default welcome msg!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeDefaultWelcomeMessage"/> + + <!--Login as a Customer with remember me checked--> + <actionGroup ref="CustomerLoginOnStorefrontWithRememberMeChecked" stepKey="loginToStorefrontAccountWithRememberMeChecked"> + <argument name="Customer" value="$$createCustomerForPersistent$$"/> + </actionGroup> + <!--Check customer name and last name in welcome message--> + <seeInCurrentUrl url="{{StorefrontCustomerDashboardPage.url}}" stepKey="seeCustomerAccountPageUrl1"/> + <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeLoggedInCustomerWelcomeMessage1"/> + + <!--Logout and check persistent customer welcome message--> + <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout1"/> + <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" wait="5" stepKey="seeCustomerSignOutPageUrl1"/> + <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$! Not you?" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seePersistentWelcomeMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php index b88b02ab4cfb5..407dad05c3baf 100644 --- a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php @@ -34,44 +34,14 @@ class AdditionalTest extends \PHPUnit\Framework\TestCase protected $contextMock; /** - * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManagerMock; + private $jsonSerializerMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Persistent\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfigMock; - - /** - * @var \Magento\Framework\App\Cache\StateInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cacheStateMock; - - /** - * @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cacheMock; - - /** - * @var \Magento\Framework\Session\SidResolverInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sidResolverMock; - - /** - * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject - */ - protected $escaperMock; - - /** - * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $urlBuilderMock; + private $persistentHelperMock; /** * @var \Magento\Persistent\Block\Header\Additional @@ -93,17 +63,7 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->contextMock = $this->createPartialMock(\Magento\Framework\View\Element\Template\Context::class, [ - 'getEventManager', - 'getScopeConfig', - 'getCacheState', - 'getCache', - 'getInlineTranslation', - 'getSidResolver', - 'getSession', - 'getEscaper', - 'getUrlBuilder' - ]); + $this->contextMock = $this->createPartialMock(\Magento\Framework\View\Element\Template\Context::class, []); $this->customerViewHelperMock = $this->createMock(\Magento\Customer\Helper\View::class); $this->persistentSessionHelperMock = $this->createPartialMock( \Magento\Persistent\Helper\Session::class, @@ -119,103 +79,14 @@ protected function setUp() ['getById'] ); - $this->eventManagerMock = $this->getMockForAbstractClass( - \Magento\Framework\Event\ManagerInterface::class, - [], - '', - false, - true, - true, - ['dispatch'] - ); - $this->scopeConfigMock = $this->getMockForAbstractClass( - \Magento\Framework\App\Config\ScopeConfigInterface::class, - [], - '', - false, - true, - true, - ['getValue'] - ); - $this->cacheStateMock = $this->getMockForAbstractClass( - \Magento\Framework\App\Cache\StateInterface::class, - [], - '', - false, - true, - true, - ['isEnabled'] + $this->jsonSerializerMock = $this->createPartialMock( + \Magento\Framework\Serialize\Serializer\Json::class, + ['serialize'] ); - $this->cacheMock = $this->getMockForAbstractClass( - \Magento\Framework\App\CacheInterface::class, - [], - '', - false, - true, - true, - ['load'] + $this->persistentHelperMock = $this->createPartialMock( + \Magento\Persistent\Helper\Data::class, + ['getLifeTime'] ); - $this->sidResolverMock = $this->getMockForAbstractClass( - \Magento\Framework\Session\SidResolverInterface::class, - [], - '', - false, - true, - true, - ['getSessionIdQueryParam'] - ); - $this->sessionMock = $this->getMockForAbstractClass( - \Magento\Framework\Session\SessionManagerInterface::class, - [], - '', - false, - true, - true, - ['getSessionId'] - ); - $this->escaperMock = $this->getMockForAbstractClass( - \Magento\Framework\Escaper::class, - [], - '', - false, - true, - true, - ['escapeHtml'] - ); - $this->urlBuilderMock = $this->getMockForAbstractClass( - \Magento\Framework\UrlInterface::class, - [], - '', - false, - true, - true, - ['getUrl'] - ); - - $this->contextMock->expects($this->once()) - ->method('getEventManager') - ->willReturn($this->eventManagerMock); - $this->contextMock->expects($this->once()) - ->method('getScopeConfig') - ->willReturn($this->scopeConfigMock); - $this->contextMock->expects($this->once()) - ->method('getCacheState') - ->willReturn($this->cacheStateMock); - $this->contextMock->expects($this->once()) - ->method('getCache') - ->willReturn($this->cacheMock); - $this->contextMock->expects($this->once()) - ->method('getSidResolver') - ->willReturn($this->sidResolverMock); - $this->contextMock->expects($this->once()) - ->method('getSession') - ->willReturn($this->sessionMock); - $this->contextMock->expects($this->once()) - ->method('getEscaper') - ->willReturn($this->escaperMock); - $this->contextMock->expects($this->once()) - ->method('getUrlBuilder') - ->willReturn($this->urlBuilderMock); $this->additional = $this->objectManager->getObject( \Magento\Persistent\Block\Header\Additional::class, @@ -224,91 +95,48 @@ protected function setUp() 'customerViewHelper' => $this->customerViewHelperMock, 'persistentSessionHelper' => $this->persistentSessionHelperMock, 'customerRepository' => $this->customerRepositoryMock, - 'data' => [] + 'data' => [], + 'jsonSerializer' => $this->jsonSerializerMock, + 'persistentHelper' => $this->persistentHelperMock, ] ); } /** - * Run test toHtml method - * - * @param bool $customerId * @return void - * - * @dataProvider dataProviderToHtml */ - public function testToHtml($customerId) + public function testGetCustomerId(): void { - $cacheData = false; - $idQueryParam = 'id-query-param'; - $sessionId = 'session-id'; - - $this->additional->setData('cache_lifetime', 789); - $this->additional->setData('cache_key', 'cache-key'); - - $this->eventManagerMock->expects($this->at(0)) - ->method('dispatch') - ->with('view_block_abstract_to_html_before', ['block' => $this->additional]); - $this->eventManagerMock->expects($this->at(1)) - ->method('dispatch') - ->with('view_block_abstract_to_html_after'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with( - 'advanced/modules_disable_output/Magento_Persistent', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->willReturn(false); - - // get cache - $this->cacheStateMock->expects($this->at(0)) - ->method('isEnabled') - ->with(\Magento\Persistent\Block\Header\Additional::CACHE_GROUP) - ->willReturn(true); - // save cache - $this->cacheStateMock->expects($this->at(1)) - ->method('isEnabled') - ->with(\Magento\Persistent\Block\Header\Additional::CACHE_GROUP) - ->willReturn(false); - - $this->cacheMock->expects($this->once()) - ->method('load') - ->willReturn($cacheData); - $this->sidResolverMock->expects($this->never()) - ->method('getSessionIdQueryParam') - ->with($this->sessionMock) - ->willReturn($idQueryParam); - $this->sessionMock->expects($this->never()) - ->method('getSessionId') - ->willReturn($sessionId); - - // call protected _toHtml method + $customerId = 1; + /** @var \Magento\Persistent\Model\Session|\PHPUnit_Framework_MockObject_MockObject $sessionMock */ $sessionMock = $this->createPartialMock(\Magento\Persistent\Model\Session::class, ['getCustomerId']); - - $this->persistentSessionHelperMock->expects($this->atLeastOnce()) - ->method('getSession') - ->willReturn($sessionMock); - - $sessionMock->expects($this->atLeastOnce()) + $sessionMock->expects($this->once()) ->method('getCustomerId') ->willReturn($customerId); + $this->persistentSessionHelperMock->expects($this->once()) + ->method('getSession') + ->willReturn($sessionMock); - if ($customerId) { - $this->assertEquals('<span><a >Not you?</a></span>', $this->additional->toHtml()); - } else { - $this->assertEquals('', $this->additional->toHtml()); - } + $this->assertEquals($customerId, $this->additional->getCustomerId()); } /** - * Data provider for dataProviderToHtml method - * - * @return array + * @return void */ - public function dataProviderToHtml() + public function testGetConfig(): void { - return [ - ['customerId' => 2], - ['customerId' => null], - ]; + $lifeTime = 500; + $arrayToSerialize = ['expirationLifetime' => $lifeTime]; + $serializedArray = '{"expirationLifetime":' . $lifeTime . '}'; + + $this->persistentHelperMock->expects($this->once()) + ->method('getLifeTime') + ->willReturn($lifeTime); + $this->jsonSerializerMock->expects($this->once()) + ->method('serialize') + ->with($arrayToSerialize) + ->willReturn($serializedArray); + + $this->assertEquals($serializedArray, $this->additional->getConfig()); } } diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php index b9285715146a5..c7f84b476fa7e 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php @@ -102,8 +102,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderCartConvertsToGuest ['setCustomerEmail', 'getAddressesCollection'], false ); - $this->checkoutSessionMock->expects($this->once())->method('getQuote')->willReturn($quoteMock); - $quoteMock->expects($this->once())->method('getId')->willReturn($cartId); + $this->checkoutSessionMock->method('getQuoteId')->willReturn($cartId); $this->cartRepositoryMock->expects($this->once())->method('get')->with($cartId)->willReturn($quoteMock); $quoteMock->expects($this->once())->method('setCustomerEmail')->with($email); /** @var \Magento\Framework\Data\Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ diff --git a/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php index 7008a9eb25e5d..6d4db70adc642 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php @@ -80,31 +80,18 @@ protected function setUp() ); } - public function testEmulateWelcomeBlock() + /** + * @return void + */ + public function testEmulateWelcomeBlock(): void { - $customerId = 1; - $customerName = 'Test Customer Name'; - $welcomeMessage = __('Welcome, %1!', $customerName); - $customerMock = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + $welcomeMessage = __(' '); $block = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) ->disableOriginalConstructor() ->setMethods(['setWelcome']) ->getMock(); - $headerAdditionalBlock = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) - ->disableOriginalConstructor() - ->getMock(); - $this->persistentSessionMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->sessionMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); - $this->customerRepositoryMock - ->expects($this->once()) - ->method('getById') - ->with($customerId)->willReturn($customerMock); - $this->customerViewHelperMock->expects($this->once())->method('getCustomerName')->willReturn($customerName); - $this->layoutMock->expects($this->once()) - ->method('getBlock') - ->with('header.additional') - ->willReturn($headerAdditionalBlock); $block->expects($this->once())->method('setWelcome')->with($welcomeMessage); + $this->observer->emulateWelcomeBlock($block); } } diff --git a/app/code/Magento/Persistent/etc/di.xml b/app/code/Magento/Persistent/etc/di.xml index f49d4361acb52..c28426b4f25bf 100644 --- a/app/code/Magento/Persistent/etc/di.xml +++ b/app/code/Magento/Persistent/etc/di.xml @@ -12,4 +12,7 @@ <type name="Magento\Customer\CustomerData\Customer"> <plugin name="section_data" type="Magento\Persistent\Model\Plugin\CustomerData" /> </type> + <type name="Magento\Framework\App\Http\Context"> + <plugin name="persistent_page_cache_variation" type="Magento\Persistent\Model\Plugin\PersistentCustomerContext" /> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/frontend/di.xml b/app/code/Magento/Persistent/etc/frontend/di.xml index f976f4de79c21..3c33f8a51c418 100644 --- a/app/code/Magento/Persistent/etc/frontend/di.xml +++ b/app/code/Magento/Persistent/etc/frontend/di.xml @@ -35,4 +35,18 @@ <type name="Magento\Checkout\Model\GuestPaymentInformationManagement"> <plugin name="inject_guest_address_for_nologin" type="Magento\Persistent\Model\Checkout\GuestPaymentInformationManagementPlugin" /> </type> + <type name="Magento\Customer\CustomerData\SectionPoolInterface"> + <arguments> + <argument name="sectionSourceMap" xsi:type="array"> + <item name="persistent" xsi:type="string">Magento\Persistent\CustomerData\Persistent</item> + </argument> + </arguments> + </type> + <type name="Magento\Customer\Block\CustomerData"> + <arguments> + <argument name="expirableSectionNames" xsi:type="array"> + <item name="persistent" xsi:type="string">persistent</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/frontend/sections.xml b/app/code/Magento/Persistent/etc/frontend/sections.xml new file mode 100644 index 0000000000000..16b44c502fc47 --- /dev/null +++ b/app/code/Magento/Persistent/etc/frontend/sections.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd"> + <action name="persistent/index/unsetCookie"> + <section name="persistent"/> + </action> +</config> diff --git a/app/code/Magento/Persistent/view/frontend/requirejs-config.js b/app/code/Magento/Persistent/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..e30e07c454be5 --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_Customer/js/customer-data': { + 'Magento_Persistent/js/view/customer-data-mixin': true + } + } + } +}; diff --git a/app/code/Magento/Persistent/view/frontend/templates/additional.phtml b/app/code/Magento/Persistent/view/frontend/templates/additional.phtml new file mode 100644 index 0000000000000..28dce5dc23cc9 --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/templates/additional.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<?php if ($block->getCustomerId()) :?> + <span> + <a <?= /* @escapeNotVerified */ $block->getLinkAttributes()?>><?= $block->escapeHtml(__('Not you?'));?></a> + </span> +<?php endif;?> +<script type="application/javascript"> + window.persistent = <?= /* @noEscape */ $block->getConfig(); ?>; +</script> +<script type="text/x-magento-init"> + { + "li.greet.welcome > span.not-logged-in": { + "Magento_Persistent/js/view/additional-welcome": {} + } + } +</script> diff --git a/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js new file mode 100644 index 0000000000000..7ace6e60d1c39 --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js @@ -0,0 +1,55 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'mage/translate', + 'Magento_Customer/js/customer-data' +], function ($, $t, customerData) { + 'use strict'; + + return { + /** + * Init. + */ + init: function () { + var persistent = customerData.get('persistent'); + + if (persistent().fullname === undefined) { + customerData.get('persistent').subscribe(this.replacePersistentWelcome); + } else { + this.replacePersistentWelcome(); + } + }, + + /** + * Replace welcome message for customer with persistent cookie. + */ + replacePersistentWelcome: function () { + var persistent = customerData.get('persistent'), + welcomeElems; + + if (persistent().fullname !== undefined) { + welcomeElems = $('li.greet.welcome > span.not-logged-in'); + + if (welcomeElems.length) { + $(welcomeElems).each(function () { + var html = $t('Welcome, %1!').replace('%1', persistent().fullname); + + $(this).attr('data-bind', html); + $(this).html(html); + }); + } + } + }, + + /** + * @constructor + */ + 'Magento_Persistent/js/view/additional-welcome': function () { + this.init(); + } + }; +}); diff --git a/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js b/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js new file mode 100644 index 0000000000000..855404c6f6f32 --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js @@ -0,0 +1,51 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper' +], function ($, wrapper) { + 'use strict'; + + var mixin = { + + /** + * Check if persistent section is expired due to lifetime. + * + * @param {Function} originFn - Original method. + * @return {Array} + */ + getExpiredSectionNames: function (originFn) { + var expiredSections = originFn(), + storage = $.initNamespaceStorage('mage-cache-storage').localStorage, + currentTimestamp = Math.floor(Date.now() / 1000), + persistentIndex = expiredSections.indexOf('persistent'), + persistentLifeTime = 0, + sectionData; + + if (window.persistent !== undefined && window.persistent.expirationLifetime !== undefined) { + persistentLifeTime = window.persistent.expirationLifetime; + } + + if (persistentIndex !== -1) { + sectionData = storage.get('persistent'); + + if (typeof sectionData === 'object' && + sectionData['data_id'] + persistentLifeTime >= currentTimestamp + ) { + expiredSections.splice(persistentIndex, 1); + } + } + + return expiredSections; + } + }; + + /** + * Override default customer-data.getExpiredSectionNames(). + */ + return function (target) { + return wrapper.extend(target, mixin); + }; +}); diff --git a/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php b/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php index 710ede8ecefa6..c7b3d59138ecc 100644 --- a/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php @@ -5,6 +5,8 @@ */ namespace Magento\ProductAlert\Model\ResourceModel; +use Magento\Framework\Model\AbstractModel; + /** * Product alert for back in abstract resource model * @@ -15,13 +17,13 @@ abstract class AbstractResource extends \Magento\Framework\Model\ResourceModel\D /** * Retrieve alert row by object parameters * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return array|false */ - protected function _getAlertRow(\Magento\Framework\Model\AbstractModel $object) + protected function _getAlertRow(AbstractModel $object) { $connection = $this->getConnection(); - if ($object->getCustomerId() && $object->getProductId() && $object->getWebsiteId()) { + if ($this->isExistAllBindIds($object)) { $select = $connection->select()->from( $this->getMainTable() )->where( @@ -30,24 +32,41 @@ protected function _getAlertRow(\Magento\Framework\Model\AbstractModel $object) 'product_id = :product_id' )->where( 'website_id = :website_id' + )->where( + 'store_id = :store_id' ); $bind = [ ':customer_id' => $object->getCustomerId(), ':product_id' => $object->getProductId(), ':website_id' => $object->getWebsiteId(), + ':store_id' => $object->getStoreId() ]; return $connection->fetchRow($select, $bind); } return false; } + /** + * Is exists all bind ids. + * + * @param AbstractModel $object + * @return bool + */ + private function isExistAllBindIds(AbstractModel $object): bool + { + return ($object->getCustomerId() + && $object->getProductId() + && $object->getWebsiteId() + && $object->getStoreId()); + } + /** * Load object data by parameters * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return $this */ - public function loadByParam(\Magento\Framework\Model\AbstractModel $object) + public function loadByParam(AbstractModel $object) { $row = $this->_getAlertRow($object); if ($row) { @@ -59,13 +78,13 @@ public function loadByParam(\Magento\Framework\Model\AbstractModel $object) /** * Delete all customer alerts on website * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @param int $customerId * @param int $websiteId * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function deleteCustomer(\Magento\Framework\Model\AbstractModel $object, $customerId, $websiteId = null) + public function deleteCustomer(AbstractModel $object, $customerId, $websiteId = null) { $connection = $this->getConnection(); $where = []; diff --git a/app/code/Magento/Quote/Api/CartRepositoryInterface.php b/app/code/Magento/Quote/Api/CartRepositoryInterface.php index f507c1e83f10f..ee122d1b02ffd 100644 --- a/app/code/Magento/Quote/Api/CartRepositoryInterface.php +++ b/app/code/Magento/Quote/Api/CartRepositoryInterface.php @@ -25,10 +25,9 @@ public function get($cartId); * Enables administrative users to list carts that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CartRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CartRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * - * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria * @return \Magento\Quote\Api\Data\CartSearchResultsInterface */ diff --git a/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php b/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php index a9d1772684ba6..f1ee8bd83fe93 100644 --- a/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php +++ b/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php @@ -37,7 +37,7 @@ public function get($cartId); * List available payment methods for a specified shopping cart. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#GuestPaymentMethodManagementInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#GuestPaymentMethodManagementInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param string $cartId The cart ID. diff --git a/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php b/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php index 50fac772ed3d9..b00a6617beaeb 100644 --- a/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php +++ b/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php @@ -37,7 +37,7 @@ public function get($cartId); * Lists available payment methods for a specified shopping cart. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param int $cartId The cart ID. diff --git a/app/code/Magento/Quote/Model/Quote/Address/CustomAttributeListInterface.php b/app/code/Magento/Quote/Model/Quote/Address/CustomAttributeListInterface.php index 4d642c31ae16f..313ad23f2215b 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/CustomAttributeListInterface.php +++ b/app/code/Magento/Quote/Model/Quote/Address/CustomAttributeListInterface.php @@ -12,7 +12,7 @@ interface CustomAttributeListInterface { /** - * Retrieve list of quote addresss custom attributes + * Retrieve list of quote address custom attributes * * @return array */ 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 84f1fc1c35adf..e9a63dad6e169 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php @@ -9,6 +9,9 @@ use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Model\Quote\Address\FreeShippingInterface; +/** + * Collect totals for shipping. + */ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal { /** @@ -111,7 +114,7 @@ public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Qu { $amount = $total->getShippingAmount(); $shippingDescription = $total->getShippingDescription(); - $title = ($amount != 0 && $shippingDescription) + $title = ($shippingDescription) ? __('Shipping & Handling (%1)', $shippingDescription) : __('Shipping & Handling'); @@ -227,7 +230,7 @@ private function getAssignmentWeightData(AddressInterface $address, array $items * @param bool $addressFreeShipping * @param float $itemWeight * @param float $itemQty - * @param $freeShipping + * @param bool $freeShipping * @return float */ private function getItemRowWeight( diff --git a/app/code/Magento/Quote/Model/Quote/Item/AbstractItem.php b/app/code/Magento/Quote/Model/Quote/Item/AbstractItem.php index 1aca6d6c88cdc..a6fa6828a04e7 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/AbstractItem.php +++ b/app/code/Magento/Quote/Model/Quote/Item/AbstractItem.php @@ -418,6 +418,7 @@ public function calcRowTotal() /** * Get item price used for quote calculation process. + * * This method get custom price (if it is defined) or original product final price * * @return float @@ -438,6 +439,7 @@ public function getCalculationPrice() /** * Get item price used for quote calculation process. + * * This method get original custom price applied before tax calculation * * @return float @@ -502,6 +504,7 @@ public function getBaseCalculationPriceOriginal() /** * Get original price (retrieved from product) for item. + * * Original price value is in quote selected currency * * @return float @@ -519,8 +522,8 @@ public function getOriginalPrice() /** * Set original price to item (calculation price will be refreshed too) * - * @param float $price - * @return \Magento\Quote\Model\Quote\Item\AbstractItem + * @param float $price + * @return \Magento\Quote\Model\Quote\Item\AbstractItem */ public function setOriginalPrice($price) { @@ -538,10 +541,10 @@ public function getBaseOriginalPrice() } /** - * Specify custom item price (used in case whe we have apply not product price to item) + * Specify custom item price (used in case when we have apply not product price to item) * - * @param float $value - * @return \Magento\Quote\Model\Quote\Item\AbstractItem + * @param float $value + * @return \Magento\Quote\Model\Quote\Item\AbstractItem */ public function setCustomPrice($value) { @@ -563,8 +566,8 @@ public function getPrice() /** * Specify item price (base calculation price and converted price will be refreshed too) * - * @param float $value - * @return $this + * @param float $value + * @return $this */ public function setPrice($value) { @@ -575,6 +578,7 @@ public function setPrice($value) /** * Get item price converted to quote currency + * * @return float */ public function getConvertedPrice() @@ -589,6 +593,7 @@ public function getConvertedPrice() /** * Set new value for converted price + * * @param float $value * @return $this */ @@ -614,8 +619,7 @@ public function __clone() } /** - * Checking if there children calculated or parent item - * when we have parent quote item and its children + * Checking if there children calculated or parent item when we have parent quote item and its children * * @return bool */ @@ -636,6 +640,8 @@ public function isChildrenCalculated() } /** + * Checking can we ship product separately + * * Checking can we ship product separately (each child separately) * or each parent product item can be shipped only like one item * @@ -658,8 +664,9 @@ public function isShipSeparately() } /** - * Returns the total discount amounts of all the child items. If there are no children, returns the discount - * amount of this item. + * Returns the total discount amounts of all the child items. + * + * If there are no children, returns the discount amount of this item. * * @return float */ diff --git a/app/code/Magento/Quote/Model/Quote/Item/Updater.php b/app/code/Magento/Quote/Model/Quote/Item/Updater.php index 6a7a3c1c1839e..9865ae82ac3d6 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Updater.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Updater.php @@ -60,6 +60,7 @@ public function __construct( /** * Update quote item qty. + * * Custom price is updated in case 'custom_price' value exists * * @param Item $item @@ -145,8 +146,8 @@ protected function unsetCustomPrice(Item $item) $item->addOption($infoBuyRequest); } - $item->unsetData('custom_price'); - $item->unsetData('original_custom_price'); + $item->setData('custom_price', null); + $item->setData('original_custom_price', null); } /** diff --git a/app/code/Magento/Quote/Model/QuoteValidator.php b/app/code/Magento/Quote/Model/QuoteValidator.php index 062cf76bcaa1a..e67a0f1356262 100644 --- a/app/code/Magento/Quote/Model/QuoteValidator.php +++ b/app/code/Magento/Quote/Model/QuoteValidator.php @@ -25,7 +25,7 @@ class QuoteValidator /** * Maximum available number */ - const MAXIMUM_AVAILABLE_NUMBER = 99999999; + const MAXIMUM_AVAILABLE_NUMBER = 10000000000000000; /** * @var AllowedCountries diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/UpdaterTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/UpdaterTest.php index 7933da7c5fe37..8e6a3723caa7c 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/UpdaterTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/UpdaterTest.php @@ -67,7 +67,7 @@ protected function setUp() 'addOption', 'setCustomPrice', 'setOriginalCustomPrice', - 'unsetData', + 'setData', 'hasData', 'setIsQtyDecimal' ]); @@ -301,7 +301,7 @@ public function testUpdateUnsetCustomPrice() 'setProduct', 'getData', 'unsetData', - 'hasData' + 'hasData', ]); $buyRequestMock->expects($this->never())->method('setCustomPrice'); $buyRequestMock->expects($this->once())->method('getData')->will($this->returnValue([])); @@ -353,7 +353,11 @@ public function testUpdateUnsetCustomPrice() ->will($this->returnValue($buyRequestMock)); $this->itemMock->expects($this->exactly(2)) - ->method('unsetData'); + ->method('setData') + ->withConsecutive( + ['custom_price', null], + ['original_custom_price', null] + ); $this->itemMock->expects($this->once()) ->method('hasData') diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 7dd215b87e8cc..3361d497d8d8d 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -37,9 +37,9 @@ comment="Store Currency Code"/> <column xsi:type="varchar" name="quote_currency_code" nullable="true" length="255" comment="Quote Currency Code"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Grand Total"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Grand Total"/> <column xsi:type="varchar" name="checkout_method" nullable="true" length="255" comment="Checkout Method"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" @@ -68,19 +68,19 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="255" comment="Coupon Code"/> <column xsi:type="varchar" name="global_currency_code" nullable="true" length="255" comment="Global Currency Code"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_to_quote_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_quote_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Quote Rate"/> <column xsi:type="varchar" name="customer_taxvat" nullable="true" length="255" comment="Customer Taxvat"/> <column xsi:type="varchar" name="customer_gender" nullable="true" length="255" comment="Customer Gender"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal With Discount"/> - <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal With Discount"/> <column xsi:type="int" name="is_changed" padding="10" unsigned="true" nullable="true" identity="false" comment="Is Changed"/> @@ -143,57 +143,57 @@ comment="Shipping Description"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Weight"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Subtotal"/> - <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal With Discount"/> - <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Subtotal With Discount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Shipping Amount"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Grand Total"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Grand Total"/> <column xsi:type="text" name="customer_notes" nullable="true" comment="Customer Notes"/> <column xsi:type="text" name="applied_taxes" nullable="true" comment="Applied Taxes"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> - <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_subtotal_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="text" name="vat_id" nullable="true" comment="Vat Id"/> <column xsi:type="smallint" name="vat_is_valid" padding="6" unsigned="false" nullable="true" identity="false" @@ -249,45 +249,45 @@ comment="Custom Price"/> <column xsi:type="decimal" name="discount_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Discount Percent"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Tax Percent"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Row Total With Discount"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> <column xsi:type="varchar" name="product_type" nullable="true" length="255" comment="Product Type"/> - <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Before Discount"/> - <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Before Discount"/> <column xsi:type="decimal" name="original_custom_price" scale="4" precision="12" unsigned="false" nullable="true" comment="Original Custom Price"/> <column xsi:type="varchar" name="redirect_url" nullable="true" length="255" comment="Redirect Url"/> <column xsi:type="decimal" name="base_cost" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Cost"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="item_id"/> @@ -330,19 +330,19 @@ comment="Weight"/> <column xsi:type="decimal" name="qty" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Qty"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Row Total With Discount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> @@ -370,17 +370,17 @@ comment="Base Price"/> <column xsi:type="decimal" name="base_cost" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Cost"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="address_item_id"/> @@ -472,7 +472,7 @@ <column xsi:type="varchar" name="code" nullable="true" length="255" comment="Code"/> <column xsi:type="varchar" name="method" nullable="true" length="255" comment="Method"/> <column xsi:type="text" name="method_description" nullable="true" comment="Method Description"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="price" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Price"/> <column xsi:type="text" name="error_message" nullable="true" comment="Error Message"/> <column xsi:type="text" name="method_title" nullable="true" comment="Method Title"/> diff --git a/app/code/Magento/QuoteAnalytics/README.md b/app/code/Magento/QuoteAnalytics/README.md index d4adcc9313229..c5a3857c7af3d 100644 --- a/app/code/Magento/QuoteAnalytics/README.md +++ b/app/code/Magento/QuoteAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_QuoteAnalytics -The Magento_QuoteAnalytics module configures data definitions for a data collection related to the Quote module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_QuoteAnalytics module configures data definitions for a data collection related to the Quote module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index 9c50d4b85578b..c3207bf478bbe 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -8,7 +8,7 @@ namespace Magento\QuoteGraphQl\Model\Cart; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; @@ -47,7 +47,7 @@ public function __construct( * @param string $cartHash * @param int|null $userId * @return Quote - * @throws GraphQlAuthenticationException + * @throws GraphQlAuthorizationException * @throws GraphQlNoSuchEntityException */ public function execute(string $cartHash, ?int $userId): Quote @@ -77,7 +77,7 @@ public function execute(string $cartHash, ?int $userId): Quote } if ($customerId !== $userId) { - throw new GraphQlAuthenticationException( + throw new GraphQlAuthorizationException( __( 'The current user cannot perform operations on cart "%masked_cart_id"', ['masked_cart_id' => $cartHash] diff --git a/app/code/Magento/ReleaseNotification/README.md b/app/code/Magento/ReleaseNotification/README.md index df3206a176f09..1f6cac764b565 100644 --- a/app/code/Magento/ReleaseNotification/README.md +++ b/app/code/Magento/ReleaseNotification/README.md @@ -50,4 +50,4 @@ A clickable link to internal or external content in any text field will be creat ### Link Format Example: -The text: `http://devdocs.magento.com/ [Magento DevDocs].` will appear as [Magento DevDocs](http://devdocs.magento.com/). +The text: `https://devdocs.magento.com/ [Magento DevDocs].` will appear as [Magento DevDocs](https://devdocs.magento.com/). diff --git a/app/code/Magento/ReleaseNotification/Ui/DataProvider/NotificationDataProvider.php b/app/code/Magento/ReleaseNotification/Ui/DataProvider/NotificationDataProvider.php index cdf7e0c6ac7c2..a1e558a5a2f84 100644 --- a/app/code/Magento/ReleaseNotification/Ui/DataProvider/NotificationDataProvider.php +++ b/app/code/Magento/ReleaseNotification/Ui/DataProvider/NotificationDataProvider.php @@ -82,7 +82,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getData() { @@ -95,7 +95,7 @@ public function getData() } /** - * {@inheritdoc} + * @inheritdoc */ public function getMeta() { @@ -107,7 +107,7 @@ public function getMeta() } /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -115,15 +115,15 @@ public function getName() } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfigData() { - return isset($this->data['config']) ? $this->data['config'] : []; + return $this->data['config'] ?? []; } /** - * {@inheritdoc} + * @inheritdoc */ public function setConfigData($config) { @@ -133,7 +133,7 @@ public function setConfigData($config) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getFieldMetaInfo($fieldSetName, $fieldName) @@ -142,7 +142,7 @@ public function getFieldMetaInfo($fieldSetName, $fieldName) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getFieldSetMetaInfo($fieldSetName) @@ -151,7 +151,7 @@ public function getFieldSetMetaInfo($fieldSetName) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getFieldsMetaInfo($fieldSetName) @@ -160,7 +160,7 @@ public function getFieldsMetaInfo($fieldSetName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getPrimaryFieldName() { @@ -168,7 +168,7 @@ public function getPrimaryFieldName() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRequestFieldName() { @@ -176,31 +176,28 @@ public function getRequestFieldName() } /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @inheritdoc */ public function addFilter(\Magento\Framework\Api\Filter $filter) { } /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @inheritdoc */ public function addOrder($field, $direction) { } /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @inheritdoc */ public function setLimit($offset, $size) { } /** - * {@inheritdoc} + * @inheritdoc */ public function getSearchCriteria() { @@ -208,7 +205,7 @@ public function getSearchCriteria() } /** - * {@inheritdoc} + * @inheritdoc */ public function getSearchResult() { diff --git a/app/code/Magento/ReleaseNotification/i18n/en_US.csv b/app/code/Magento/ReleaseNotification/i18n/en_US.csv index 426cdc0e863d2..178482dc7a980 100644 --- a/app/code/Magento/ReleaseNotification/i18n/en_US.csv +++ b/app/code/Magento/ReleaseNotification/i18n/en_US.csv @@ -5,11 +5,11 @@ "<![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href=""http://devdocs.magento.com/magento-release-information.html"" + <a href=""https://devdocs.magento.com/magento-release-information.html"" target=""_blank"">DevDocs' Release Information</a>. </p>]]>","<![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href=""http://devdocs.magento.com/magento-release-information.html"" + <a href=""https://devdocs.magento.com/magento-release-information.html"" target=""_blank"">DevDocs' Release Information</a>. </p>]]>" diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml index 3134b2a8af21e..9c6d152bed27b 100644 --- a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml @@ -67,7 +67,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -127,7 +127,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -208,7 +208,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -289,7 +289,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> diff --git a/app/code/Magento/Review/Block/Product/ReviewRenderer.php b/app/code/Magento/Review/Block/Product/ReviewRenderer.php index 3cd15aba30420..3183196ebf30c 100644 --- a/app/code/Magento/Review/Block/Product/ReviewRenderer.php +++ b/app/code/Magento/Review/Block/Product/ReviewRenderer.php @@ -9,7 +9,11 @@ use Magento\Catalog\Block\Product\ReviewRendererInterface; use Magento\Catalog\Model\Product; +use Magento\Review\Observer\PredispatchReviewObserver; +/** + * Class ReviewRenderer + */ class ReviewRenderer extends \Magento\Framework\View\Element\Template implements ReviewRendererInterface { /** @@ -43,6 +47,19 @@ public function __construct( parent::__construct($context, $data); } + /** + * Review module availability + * + * @return string + */ + public function isReviewEnabled() : string + { + return $this->_scopeConfig->getValue( + PredispatchReviewObserver::XML_PATH_REVIEW_ACTIVE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + /** * Get review summary html * diff --git a/app/code/Magento/Review/Model/ResourceModel/Rating.php b/app/code/Magento/Review/Model/ResourceModel/Rating.php index 78020ce3f8184..37a93d40b1107 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Rating.php +++ b/app/code/Magento/Review/Model/ResourceModel/Rating.php @@ -5,6 +5,9 @@ */ namespace Magento\Review\Model\ResourceModel; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; + /** * Rating resource model * @@ -12,6 +15,7 @@ * * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Rating extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { @@ -34,13 +38,19 @@ class Rating extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $_logger; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Module\Manager $moduleManager * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Review\Model\ResourceModel\Review\Summary $reviewSummary + * @param Review\Summary $reviewSummary * @param string $connectionName + * @param ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -48,12 +58,14 @@ public function __construct( \Magento\Framework\Module\Manager $moduleManager, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Review\Model\ResourceModel\Review\Summary $reviewSummary, - $connectionName = null + $connectionName = null, + ScopeConfigInterface $scopeConfig = null ) { $this->moduleManager = $moduleManager; $this->_storeManager = $storeManager; $this->_logger = $logger; $this->_reviewSummary = $reviewSummary; + $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); parent::__construct($context, $connectionName); } @@ -286,7 +298,12 @@ protected function insertRatingData($table, array $data) protected function _afterDelete(\Magento\Framework\Model\AbstractModel $object) { parent::_afterDelete($object); - if (!$this->moduleManager->isEnabled('Magento_Review')) { + if (!$this->moduleManager->isEnabled('Magento_Review') && + !$this->scopeConfig->getValue( + \Magento\Review\Observer\PredispatchReviewObserver::XML_PATH_REVIEW_ACTIVE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + ) { return $this; } $data = $this->_getEntitySummaryData($object); diff --git a/app/code/Magento/Review/Observer/PredispatchReviewObserver.php b/app/code/Magento/Review/Observer/PredispatchReviewObserver.php new file mode 100644 index 0000000000000..bdca0f5ecb1ec --- /dev/null +++ b/app/code/Magento/Review/Observer/PredispatchReviewObserver.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Review\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\UrlInterface; +use Magento\Review\Block\Product\ReviewRenderer; +use Magento\Store\Model\ScopeInterface; + +/** + * Class PredispatchReviewObserver + */ +class PredispatchReviewObserver implements ObserverInterface +{ + /** + * Configuration path to review active setting + */ + const XML_PATH_REVIEW_ACTIVE = 'catalog/review/active'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var UrlInterface + */ + private $url; + + /** + * PredispatchReviewObserver constructor. + * + * @param ScopeConfigInterface $scopeConfig + * @param UrlInterface $url + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + UrlInterface $url + ) { + $this->scopeConfig = $scopeConfig; + $this->url = $url; + } + /** + * Redirect review routes to 404 when review module is disabled. + * + * @param Observer $observer + */ + public function execute(Observer $observer) + { + if (!$this->scopeConfig->getValue( + self::XML_PATH_REVIEW_ACTIVE, + ScopeInterface::SCOPE_STORE + ) + ) { + $defaultNoRouteUrl = $this->scopeConfig->getValue( + 'web/default/no_route', + ScopeInterface::SCOPE_STORE + ); + $redirectUrl = $this->url->getUrl($defaultNoRouteUrl); + $observer->getControllerAction() + ->getResponse() + ->setRedirect($redirectUrl); + } + } +} diff --git a/app/code/Magento/Review/Test/Unit/Observer/PredispatchReviewObserverTest.php b/app/code/Magento/Review/Test/Unit/Observer/PredispatchReviewObserverTest.php new file mode 100644 index 0000000000000..cb01e55e4f491 --- /dev/null +++ b/app/code/Magento/Review/Test/Unit/Observer/PredispatchReviewObserverTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Review\Test\Unit\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Review\Observer\PredispatchReviewObserver; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\Review\Observer\PredispatchReviewObserver + */ +class PredispatchReviewObserverTest extends TestCase +{ + /** + * @var Observer|\PHPUnit_Framework_MockObject_MockObject + */ + private $mockObject; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlMock; + + /** + * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $redirectMock; + + /** + * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseMock; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlMock = $this->getMockBuilder(UrlInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseMock = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setRedirect']) + ->getMockForAbstractClass(); + $this->redirectMock = $this->getMockBuilder(RedirectInterface::class) + ->getMock(); + $this->objectManager = new ObjectManager($this); + $this->mockObject = $this->objectManager->getObject( + PredispatchReviewObserver::class, + [ + 'scopeConfig' => $this->configMock, + 'url' => $this->urlMock + ] + ); + } + + /** + * Test with enabled review active config. + */ + public function testReviewEnabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getResponse', 'getData', 'setRedirect']) + ->getMockForAbstractClass(); + + $this->configMock->method('getValue') + ->with(PredispatchReviewObserver::XML_PATH_REVIEW_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(true); + $observerMock->expects($this->never()) + ->method('getData') + ->with('controller_action') + ->willReturnSelf(); + + $observerMock->expects($this->never()) + ->method('getResponse') + ->willReturnSelf(); + + $this->assertNull($this->mockObject->execute($observerMock)); + } + + /** + * Test with disabled review active config. + */ + public function testReviewDisabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getControllerAction', 'getResponse']) + ->getMockForAbstractClass(); + + $this->configMock->expects($this->at(0)) + ->method('getValue') + ->with(PredispatchReviewObserver::XML_PATH_REVIEW_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(false); + + $expectedRedirectUrl = 'https://test.com/index'; + + $this->configMock->expects($this->at(1)) + ->method('getValue') + ->with('web/default/no_route', ScopeInterface::SCOPE_STORE) + ->willReturn($expectedRedirectUrl); + + $this->urlMock->expects($this->once()) + ->method('getUrl') + ->willReturn($expectedRedirectUrl); + + $observerMock->expects($this->once()) + ->method('getControllerAction') + ->willReturnSelf(); + + $observerMock->expects($this->once()) + ->method('getResponse') + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->once()) + ->method('setRedirect') + ->with($expectedRedirectUrl); + + $this->assertNull($this->mockObject->execute($observerMock)); + } +} diff --git a/app/code/Magento/Review/etc/adminhtml/system.xml b/app/code/Magento/Review/etc/adminhtml/system.xml index c0574e9491782..a24ed29dc2c23 100644 --- a/app/code/Magento/Review/etc/adminhtml/system.xml +++ b/app/code/Magento/Review/etc/adminhtml/system.xml @@ -10,7 +10,11 @@ <section id="catalog"> <group id="review" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Product Reviews</label> - <field id="allow_guest" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Enabled</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + <field id="allow_guest" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Allow Guests to Write Reviews</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Review/etc/config.xml b/app/code/Magento/Review/etc/config.xml index 78dc87960f090..9fd9443be67ef 100644 --- a/app/code/Magento/Review/etc/config.xml +++ b/app/code/Magento/Review/etc/config.xml @@ -9,6 +9,7 @@ <default> <catalog> <review> + <active>1</active> <allow_guest>1</allow_guest> </review> </catalog> diff --git a/app/code/Magento/Review/etc/frontend/events.xml b/app/code/Magento/Review/etc/frontend/events.xml index bc94277d69709..8e883ce328a2c 100644 --- a/app/code/Magento/Review/etc/frontend/events.xml +++ b/app/code/Magento/Review/etc/frontend/events.xml @@ -12,4 +12,7 @@ <event name="catalog_block_product_list_collection"> <observer name="review" instance="Magento\Review\Observer\CatalogBlockProductCollectionBeforeToHtmlObserver" shared="false" /> </event> + <event name="controller_action_predispatch_review"> + <observer name="catalog_review_enabled" instance="Magento\Review\Observer\PredispatchReviewObserver" /> + </event> </config> diff --git a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml index d7c5c19d4d813..6fcf5b0c82b4f 100644 --- a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml @@ -9,7 +9,7 @@ <update handle="review_product_form_component"/> <body> <referenceContainer name="content"> - <block class="Magento\Cookie\Block\RequireCookie" name="require-cookie" template="Magento_Cookie::require_cookie.phtml"> + <block class="Magento\Cookie\Block\RequireCookie" name="require-cookie" template="Magento_Cookie::require_cookie.phtml" ifconfig="catalog/review/active"> <arguments> <argument name="triggers" xsi:type="array"> <item name="submitReviewButton" xsi:type="string">.review .action.submit</item> @@ -18,8 +18,8 @@ </block> </referenceContainer> <referenceBlock name="product.info.details"> - <block class="Magento\Review\Block\Product\Review" name="reviews.tab" as="reviews" template="Magento_Review::review.phtml" group="detailed_info"> - <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form"> + <block class="Magento\Review\Block\Product\Review" name="reviews.tab" as="reviews" template="Magento_Review::review.phtml" group="detailed_info" ifconfig="catalog/review/active"> + <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form" ifconfig="catalog/review/active"> <container name="product.review.form.fields.before" as="form_fields_before" label="Review Form Fields Before"/> </block> </block> diff --git a/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml b/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml index 0a7ddd8b8903d..8a853cdd2e409 100644 --- a/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml +++ b/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml @@ -9,7 +9,7 @@ <update handle="catalog_product_view"/> <body> <referenceBlock name="reviews.tab"> - <block class="Magento\Review\Block\Form\Configure" name="product.review.form" as="review_form"> + <block class="Magento\Review\Block\Form\Configure" name="product.review.form" as="review_form" ifconfig="catalog/review/active"> <arguments> <argument name="jsLayout" xsi:type="array"> <item name="components" xsi:type="array"> diff --git a/app/code/Magento/Review/view/frontend/layout/customer_account.xml b/app/code/Magento/Review/view/frontend/layout/customer_account.xml index 54d171cbf1322..9f759dba41782 100644 --- a/app/code/Magento/Review/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Review/view/frontend/layout/customer_account.xml @@ -8,7 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-product-reviews-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-product-reviews-link" ifconfig="catalog/review/active"> <arguments> <argument name="path" xsi:type="string">review/customer</argument> <argument name="label" xsi:type="string" translate="true">My Product Reviews</argument> diff --git a/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml b/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml index 73174f0570e28..2e898a539a954 100644 --- a/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml +++ b/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml @@ -8,7 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> - <block class="Magento\Review\Block\Customer\Recent" name="customer_account_dashboard_info1" template="Magento_Review::customer/recent.phtml" after="customer_account_dashboard_address" cacheable="false"/> + <block class="Magento\Review\Block\Customer\Recent" name="customer_account_dashboard_info1" template="Magento_Review::customer/recent.phtml" after="customer_account_dashboard_address" cacheable="false" ifconfig="catalog/review/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml b/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml index 2857e859aa06c..b5f7562963314 100644 --- a/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml +++ b/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml @@ -9,7 +9,7 @@ <update handle="customer_account"/> <body> <referenceContainer name="content"> - <block class="Magento\Review\Block\Customer\ListCustomer" name="review_customer_list" template="Magento_Review::customer/list.phtml" cacheable="false"/> + <block class="Magento\Review\Block\Customer\ListCustomer" name="review_customer_list" template="Magento_Review::customer/list.phtml" cacheable="false" ifconfig="catalog/review/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml b/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml index d51c89a1abe1a..d3adbd7950cf9 100644 --- a/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml +++ b/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml @@ -9,7 +9,7 @@ <update handle="customer_account"/> <body> <referenceContainer name="content"> - <block class="Magento\Review\Block\Customer\View" name="customers_review" cacheable="false"/> + <block class="Magento\Review\Block\Customer\View" name="customers_review" cacheable="false" ifconfig="catalog/review/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Review/view/frontend/layout/review_product_list.xml b/app/code/Magento/Review/view/frontend/layout/review_product_list.xml index c83cfe95d7964..8c5c1297cdda3 100644 --- a/app/code/Magento/Review/view/frontend/layout/review_product_list.xml +++ b/app/code/Magento/Review/view/frontend/layout/review_product_list.xml @@ -9,15 +9,15 @@ <update handle="catalog_product_view"/> <body> <referenceContainer name="product.info.main"> - <block class="Magento\Review\Block\Product\View\Other" name="product.info.other" as="other" template="Magento_Review::product/view/other.phtml" before="product.info.addto"/> + <block class="Magento\Review\Block\Product\View\Other" name="product.info.other" as="other" template="Magento_Review::product/view/other.phtml" before="product.info.addto" ifconfig="catalog/review/active"/> </referenceContainer> <referenceContainer name="content"> <container name="product.info.details" htmlTag="div" htmlClass="product info detailed" after="product.info.media"> - <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form"> + <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form" ifconfig="catalog/review/active"> <container name="product.review.form.fields.before" as="form_fields_before" label="Review Form Fields Before" htmlTag="div" htmlClass="rewards"/> </block> - <block class="Magento\Review\Block\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Magento_Review::product/view/list.phtml"/> - <block class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar"/> + <block class="Magento\Review\Block\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Magento_Review::product/view/list.phtml" ifconfig="catalog/review/active"/> + <block class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar" ifconfig="catalog/review/active"/> </container> </referenceContainer> </body> diff --git a/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml b/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml index af8d2dc2f506f..36fa71ea5125a 100644 --- a/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml +++ b/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml @@ -7,8 +7,8 @@ --> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root"> - <block class="Magento\Review\Block\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Magento_Review::product/view/list.phtml" /> - <block class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar"> + <block class="Magento\Review\Block\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Magento_Review::product/view/list.phtml" ifconfig="catalog/review/active"/> + <block class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar" ifconfig="catalog/review/active"> <arguments> <argument name="show_per_page" xsi:type="boolean">false</argument> <argument name="show_amounts" xsi:type="boolean">false</argument> diff --git a/app/code/Magento/Review/view/frontend/layout/review_product_view.xml b/app/code/Magento/Review/view/frontend/layout/review_product_view.xml index b70aec3f00b68..3bfc98cad9736 100644 --- a/app/code/Magento/Review/view/frontend/layout/review_product_view.xml +++ b/app/code/Magento/Review/view/frontend/layout/review_product_view.xml @@ -8,7 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> - <block class="Magento\Review\Block\View" name="review_view"/> + <block class="Magento\Review\Block\View" name="review_view" ifconfig="catalog/review/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Review/view/frontend/layout/wishlist_index_configure.xml b/app/code/Magento/Review/view/frontend/layout/wishlist_index_configure.xml index 0a7ddd8b8903d..8a853cdd2e409 100644 --- a/app/code/Magento/Review/view/frontend/layout/wishlist_index_configure.xml +++ b/app/code/Magento/Review/view/frontend/layout/wishlist_index_configure.xml @@ -9,7 +9,7 @@ <update handle="catalog_product_view"/> <body> <referenceBlock name="reviews.tab"> - <block class="Magento\Review\Block\Form\Configure" name="product.review.form" as="review_form"> + <block class="Magento\Review\Block\Form\Configure" name="product.review.form" as="review_form" ifconfig="catalog/review/active"> <arguments> <argument name="jsLayout" xsi:type="array"> <item name="components" xsi:type="array"> diff --git a/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml b/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml index da689960dfe54..23cb6699aeb21 100644 --- a/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml +++ b/app/code/Magento/Review/view/frontend/templates/helper/summary.phtml @@ -11,7 +11,7 @@ $url = $block->getReviewsUrl() . '#reviews'; $urlForm = $block->getReviewsUrl() . '#review-form'; ?> -<?php if ($block->getReviewsCount()): ?> +<?php if ($block->isReviewEnabled() && $block->getReviewsCount()): ?> <?php $rating = $block->getRatingSummary(); ?> <div class="product-reviews-summary<?= !$rating ? ' no-rating' : '' ?>" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating"> <?php if ($rating):?> @@ -35,7 +35,7 @@ $urlForm = $block->getReviewsUrl() . '#review-form'; <a class="action add" href="<?= $block->escapeUrl($urlForm) ?>"><?= $block->escapeHtml(__('Add Your Review')) ?></a> </div> </div> -<?php elseif ($block->getDisplayIfEmpty()): ?> +<?php elseif ($block->isReviewEnabled() && $block->getDisplayIfEmpty()): ?> <div class="product-reviews-summary empty"> <div class="reviews-actions"> <a class="action add" href="<?= $block->escapeUrl($urlForm) ?>"> diff --git a/app/code/Magento/Review/view/frontend/templates/helper/summary_short.phtml b/app/code/Magento/Review/view/frontend/templates/helper/summary_short.phtml index c3eb11f03fd7d..a3ff56505f06f 100644 --- a/app/code/Magento/Review/view/frontend/templates/helper/summary_short.phtml +++ b/app/code/Magento/Review/view/frontend/templates/helper/summary_short.phtml @@ -11,7 +11,7 @@ $url = $block->getReviewsUrl() . '#reviews'; $urlForm = $block->getReviewsUrl() . '#review-form'; ?> -<?php if ($block->getReviewsCount()): ?> +<?php if ($block->isReviewEnabled() && $block->getReviewsCount()): ?> <?php $rating = $block->getRatingSummary(); ?> <div class="product-reviews-summary short<?= !$rating ? ' no-rating' : '' ?>"> <?php if ($rating):?> @@ -26,7 +26,7 @@ $urlForm = $block->getReviewsUrl() . '#review-form'; <a class="action view" href="<?= $block->escapeUrl($url) ?>"><?= $block->escapeHtml($block->getReviewsCount()) ?> <span><?= ($block->getReviewsCount() == 1) ? $block->escapeHtml(__('Review')) : $block->escapeHtml(__('Reviews')) ?></span></a> </div> </div> -<?php elseif ($block->getDisplayIfEmpty()): ?> +<?php elseif ($block->isReviewEnabled() && $block->getDisplayIfEmpty()): ?> <div class="product-reviews-summary short empty"> <div class="reviews-actions"> <a class="action add" href="<?= $block->escapeUrl($urlForm) ?>"> diff --git a/app/code/Magento/ReviewAnalytics/README.md b/app/code/Magento/ReviewAnalytics/README.md index b078083dfb7dc..a8894f99ed071 100644 --- a/app/code/Magento/ReviewAnalytics/README.md +++ b/app/code/Magento/ReviewAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_ReviewAnalytics module -The Magento_ReviewAnalytics module configures data definitions for a data collection related to the Review module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_ReviewAnalytics module configures data definitions for a data collection related to the Review module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index a1987f67e47f2..d2be99757df47 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -62,7 +62,8 @@ abstract class AbstractCondition extends \Magento\Framework\DataObject implement protected $_layout; /** - * Base name for hidden elements + * Base name for hidden elements. + * * @var string */ protected $elementName = 'rule'; @@ -116,8 +117,9 @@ public function getDefaultOperatorInputByType() } /** - * Default operator options getter - * Provides all possible operator options + * Default operator options getter. + * + * Provides all possible operator options. * * @return array */ @@ -141,6 +143,8 @@ public function getDefaultOperatorOptions() } /** + * Get rule form. + * * @return Form */ public function getForm() @@ -149,6 +153,8 @@ public function getForm() } /** + * Get condition as array. + * * @param array $arrAttributes * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -195,6 +201,8 @@ public function getMappedSqlField() } /** + * Get condition as xml. + * * @return string */ public function asXml() @@ -214,6 +222,8 @@ public function asXml() } /** + * Load condition from array. + * * @param array $arr * @return $this * @SuppressWarnings(PHPMD.NPathComplexity) @@ -229,6 +239,8 @@ public function loadArray($arr) } /** + * Load condition from xml. + * * @param string|array $xml * @return $this */ @@ -242,6 +254,8 @@ public function loadXml($xml) } /** + * Load attribute options. + * * @return $this */ public function loadAttributeOptions() @@ -250,6 +264,8 @@ public function loadAttributeOptions() } /** + * Get attribute options. + * * @return array */ public function getAttributeOptions() @@ -258,6 +274,8 @@ public function getAttributeOptions() } /** + * Get attribute select options. + * * @return array */ public function getAttributeSelectOptions() @@ -270,6 +288,8 @@ public function getAttributeSelectOptions() } /** + * Get attribute name. + * * @return string */ public function getAttributeName() @@ -278,6 +298,8 @@ public function getAttributeName() } /** + * Load operator options. + * * @return $this */ public function loadOperatorOptions() @@ -300,6 +322,8 @@ public function getInputType() } /** + * Get operator select options. + * * @return array */ public function getOperatorSelectOptions() @@ -316,6 +340,8 @@ public function getOperatorSelectOptions() } /** + * Get operator name. + * * @return array */ public function getOperatorName() @@ -324,6 +350,8 @@ public function getOperatorName() } /** + * Load value options. + * * @return $this */ public function loadValueOptions() @@ -333,6 +361,8 @@ public function loadValueOptions() } /** + * Get value select options. + * * @return array */ public function getValueSelectOptions() @@ -380,6 +410,8 @@ public function isArrayOperatorType() } /** + * Get value. + * * @return mixed */ public function getValue() @@ -395,6 +427,8 @@ public function getValue() } /** + * Get value name. + * * @return array|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -446,6 +480,8 @@ public function getNewChildSelectOptions() } /** + * Get new child name. + * * @return string */ public function getNewChildName() @@ -454,6 +490,8 @@ public function getNewChildName() } /** + * Get this condition as html. + * * @return string */ public function asHtml() @@ -467,6 +505,8 @@ public function asHtml() } /** + * Get this condition with subconditions as html. + * * @return string */ public function asHtmlRecursive() @@ -475,6 +515,8 @@ public function asHtmlRecursive() } /** + * Get type element. + * * @return AbstractElement */ public function getTypeElement() @@ -493,6 +535,8 @@ public function getTypeElement() } /** + * Get type element html. + * * @return string */ public function getTypeElementHtml() @@ -501,6 +545,8 @@ public function getTypeElementHtml() } /** + * Get attribute element. + * * @return $this */ public function getAttributeElement() @@ -528,6 +574,8 @@ public function getAttributeElement() } /** + * Get attribute element html. + * * @return string */ public function getAttributeElementHtml() @@ -536,8 +584,9 @@ public function getAttributeElementHtml() } /** - * Retrieve Condition Operator element Instance - * If the operator value is empty - define first available operator value as default + * Retrieve Condition Operator element Instance. + * + * If the operator value is empty - define first available operator value as default. * * @return \Magento\Framework\Data\Form\Element\Select */ @@ -568,6 +617,8 @@ public function getOperatorElement() } /** + * Get operator element html. + * * @return string */ public function getOperatorElementHtml() @@ -587,6 +638,8 @@ public function getValueElementType() } /** + * Get value element renderer. + * * @return \Magento\Rule\Block\Editable */ public function getValueElementRenderer() @@ -598,6 +651,8 @@ public function getValueElementRenderer() } /** + * Get value element. + * * @return $this */ public function getValueElement() @@ -615,6 +670,9 @@ public function getValueElement() // date format intentionally hard-coded $elementParams['input_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; $elementParams['date_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; + $elementParams['placeholder'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; + $elementParams['autocomplete'] = 'off'; + $elementParams['readonly'] = 'true'; } return $this->getForm()->addField( $this->getPrefix() . '__' . $this->getId() . '__value', @@ -626,6 +684,8 @@ public function getValueElement() } /** + * Get value element html. + * * @return string */ public function getValueElementHtml() @@ -634,6 +694,8 @@ public function getValueElementHtml() } /** + * Get add link html. + * * @return string */ public function getAddLinkHtml() @@ -643,6 +705,8 @@ public function getAddLinkHtml() } /** + * Get remove link html. + * * @return string */ public function getRemoveLinkHtml() @@ -655,6 +719,8 @@ public function getRemoveLinkHtml() } /** + * Get chooser container html. + * * @return string */ public function getChooserContainerHtml() @@ -664,6 +730,8 @@ public function getChooserContainerHtml() } /** + * Get this condition as string. + * * @param string $format * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -674,6 +742,8 @@ public function asString($format = '') } /** + * Get this condition with subconditions as string. + * * @param int $level * @return string */ @@ -816,6 +886,8 @@ protected function _compareValues($validatedValue, $value, $strict = true) } /** + * Validate model. + * * @param \Magento\Framework\Model\AbstractModel $model * @return bool */ diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js index b094b9818364a..8e36562ebd7fe 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/rules.js +++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js @@ -220,6 +220,8 @@ define([ var elem = Element.down(elemContainer, 'input.input-text'); + jQuery(elem).trigger('contentUpdated'); + if (elem) { elem.focus(); diff --git a/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php b/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php index 354b55eb25955..3c61384d8b84f 100644 --- a/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php @@ -20,7 +20,7 @@ interface CreditmemoRepositoryInterface * Lists credit memos that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CreditmemoRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#CreditmemoRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php b/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php index 9d4f11ed0f035..161b8405f11e4 100644 --- a/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php @@ -18,7 +18,7 @@ interface InvoiceRepositoryInterface * Lists invoices that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#InvoiceRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#InvoiceRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php b/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php index e731f6f0e980c..3449d0054b7e4 100644 --- a/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php @@ -20,7 +20,7 @@ interface OrderItemRepositoryInterface * Lists order items that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#OrderItemRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#OrderItemRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/OrderRepositoryInterface.php b/app/code/Magento/Sales/Api/OrderRepositoryInterface.php index a25f463ac8618..0c3b6ab5cb02b 100644 --- a/app/code/Magento/Sales/Api/OrderRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/OrderRepositoryInterface.php @@ -20,7 +20,7 @@ interface OrderRepositoryInterface * Lists orders that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#OrderRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#OrderRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php b/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php index bfdf17440ebd3..3b3c8221596a1 100644 --- a/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php @@ -19,7 +19,7 @@ interface ShipmentRepositoryInterface * Lists shipments that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#ShipmentRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#ShipmentRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php b/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php index de459662a8321..e55b5d60d1f6c 100644 --- a/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php @@ -18,7 +18,7 @@ interface TransactionRepositoryInterface * Lists transactions that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TransactionRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TransactionRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php index 0946492711748..aac625e7c3800 100644 --- a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php @@ -48,6 +48,8 @@ public function __construct( } /** + * Set item. + * * @param \Magento\Framework\DataObject $item * @return $this */ @@ -58,6 +60,8 @@ public function setItem(\Magento\Framework\DataObject $item) } /** + * Get item. + * * @return array|null */ public function getItem() @@ -76,6 +80,8 @@ public function getOrder() } /** + * Get order item. + * * @return array|null */ public function getOrderItem() @@ -88,6 +94,8 @@ public function getOrderItem() } /** + * Get item options. + * * @return array */ public function getItemOptions() @@ -262,6 +270,23 @@ public function getTotalAmount($item) return $totalAmount; } + /** + * Return the base total amount minus discount. + * + * @param OrderItem|InvoiceItem|CreditmemoItem $item + * @return mixed + */ + public function getBaseTotalAmount($item) + { + $baseTotalAmount = $item->getBaseRowTotal() + + $item->getBaseTaxAmount() + + $item->getBaseDiscountTaxCompensationAmount() + + $item->getBaseWeeeTaxAppliedAmount() + - $item->getBaseDiscountAmount(); + + return $baseTotalAmount; + } + /** * Return HTML for item total after discount * diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php index 4acef74b810da..53ebc26d132ab 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php @@ -15,7 +15,7 @@ use Magento\Sales\Controller\Adminhtml\Order as OrderAction; /** - * Comments History tab, needs to be accessible by POST becuase of tabs mechanism. + * Comments History tab, needs to be accessible by POST because of tabs mechanism. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 58128d4f3a404..a9c14be8675ab 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -11,6 +11,7 @@ use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Api\Data\OrderStatusHistoryInterface; use Magento\Sales\Model\Order\Payment; use Magento\Sales\Model\Order\ProductOption; @@ -764,13 +765,25 @@ public function canShip() } foreach ($this->getAllItems() as $item) { - if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) { + if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && + !$item->getLockedDoShip() && !$this->isRefunded($item)) { return true; } } return false; } + /** + * Check if item is refunded. + * + * @param OrderItemInterface $item + * @return bool + */ + private function isRefunded(OrderItemInterface $item) + { + return $item->getQtyRefunded() == $item->getQtyOrdered(); + } + /** * Retrieve order edit availability * @@ -1945,7 +1958,8 @@ public function addRelatedObject(\Magento\Framework\Model\AbstractModel $object) /** * Get formatted order created date in store timezone * - * @param string $format date format type (short|medium|long|full) + * @param int $format date format type (\IntlDateFormatter::SHORT|\IntlDateFormatter::MEDIUM + * |\IntlDateFormatter::LONG|\IntlDateFormatter::FULL) * @return string */ public function getCreatedAtFormatted($format) diff --git a/app/code/Magento/Sales/Model/Order/Address.php b/app/code/Magento/Sales/Model/Order/Address.php index 083ec0089a5c0..11d8a937387d1 100644 --- a/app/code/Magento/Sales/Model/Order/Address.php +++ b/app/code/Magento/Sales/Model/Order/Address.php @@ -174,7 +174,8 @@ protected function implodeStreetValue($value) * * @param array|string $key * @param array|string $value - * @return $this + * + * @return \Magento\Framework\DataObject */ public function setData($key, $value = null) { @@ -251,7 +252,7 @@ public function getStreet() public function getStreetLine($number) { $lines = $this->getStreet(); - return isset($lines[$number - 1]) ? $lines[$number - 1] : ''; + return $lines[$number - 1] ?? ''; } //@codeCoverageIgnoreStart @@ -721,6 +722,7 @@ public function getExtensionAttributes() * @inheritdoc * * @param \Magento\Sales\Api\Data\OrderAddressExtensionInterface $extensionAttributes + * * @return $this */ public function setExtensionAttributes(\Magento\Sales\Api\Data\OrderAddressExtensionInterface $extensionAttributes) diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php index e61c2597975bb..73afd0a06f710 100644 --- a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php +++ b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php @@ -200,6 +200,7 @@ protected function initData($creditmemo, $data) { if (isset($data['shipping_amount'])) { $creditmemo->setBaseShippingAmount((double)$data['shipping_amount']); + $creditmemo->setBaseShippingInclTax((double)$data['shipping_amount']); } if (isset($data['adjustment_positive'])) { $creditmemo->setAdjustmentPositive($data['adjustment_positive']); @@ -210,6 +211,8 @@ protected function initData($creditmemo, $data) } /** + * Calculate product options. + * * @param Item $orderItem * @param int $parentQty * @return int diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php index 7ec089b882972..a7d749ec04c7d 100644 --- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php +++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php @@ -5,12 +5,14 @@ */ namespace Magento\Sales\Model\Order\Email; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Mail\Template\TransportBuilderByStore; use Magento\Sales\Model\Order\Email\Container\IdentityInterface; use Magento\Sales\Model\Order\Email\Container\Template; +/** + * Sender Builder + */ class SenderBuilder { /** @@ -29,11 +31,8 @@ class SenderBuilder protected $transportBuilder; /** - * @var TransportBuilderByStore - */ - private $transportBuilderByStore; - - /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Template $templateContainer * @param IdentityInterface $identityContainer * @param TransportBuilder $transportBuilder @@ -48,9 +47,6 @@ public function __construct( $this->templateContainer = $templateContainer; $this->identityContainer = $identityContainer; $this->transportBuilder = $transportBuilder; - $this->transportBuilderByStore = $transportBuilderByStore ?: ObjectManager::getInstance()->get( - TransportBuilderByStore::class - ); } /** @@ -110,7 +106,7 @@ protected function configureEmailTemplate() $this->transportBuilder->setTemplateIdentifier($this->templateContainer->getTemplateId()); $this->transportBuilder->setTemplateOptions($this->templateContainer->getTemplateOptions()); $this->transportBuilder->setTemplateVars($this->templateContainer->getTemplateVars()); - $this->transportBuilderByStore->setFromByStore( + $this->transportBuilder->setFromByStore( $this->identityContainer->getEmailIdentity(), $this->identityContainer->getStore()->getId() ); diff --git a/app/code/Magento/Sales/Model/Order/Payment/Info.php b/app/code/Magento/Sales/Model/Order/Payment/Info.php index 063b3eaa71f1b..fee846fe6a62c 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Info.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Info.php @@ -11,8 +11,8 @@ use Magento\Payment\Model\InfoInterface; /** - * * Payment information model + * * @api * @since 100.0.2 */ @@ -41,7 +41,7 @@ class Info extends AbstractModel implements InfoInterface * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory - * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory, + * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory * @param \Magento\Payment\Helper\Data $paymentData * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource @@ -181,7 +181,7 @@ public function getAdditionalInformation($key = null) if (null === $key) { return $this->additionalInformation; } - return isset($this->additionalInformation[$key]) ? $this->additionalInformation[$key] : null; + return $this->additionalInformation[$key] ?? null; } /** @@ -219,8 +219,7 @@ public function hasAdditionalInformation($key = null) } /** - * Initialize additional information container with data from model - * if property empty + * Initialize additional information container with data from model if property empty * * @return void */ diff --git a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php index 8d8de47ba99cf..a8acb9aa46983 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php @@ -132,10 +132,12 @@ class Transaction extends AbstractModel implements TransactionInterface * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory * @param AttributeValueFactory $customAttributeFactory * @param \Magento\Sales\Model\OrderFactory $orderFactory + * @param \Magento\Sales\Api\OrderPaymentRepositoryInterface $orderPaymentRepository + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository * @param \Magento\Framework\Stdlib\DateTime\DateTimeFactory $dateFactory * @param TransactionFactory $transactionFactory - * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource - * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection + * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource + * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -193,8 +195,7 @@ public function setTxnId($txnId) } /** - * Parent transaction ID setter - * Can set the transaction id as well + * Parent transaction ID setter Can set the transaction id as well * * @param string $parentTxnId * @param string $txnId @@ -229,8 +230,7 @@ public function setTxnType($txnType) } /** - * Parent transaction getter - * May attempt to load it. + * Parent transaction getter. May attempt to load it. * * @param bool $shouldLoad * @return bool|\Magento\Sales\Model\Order\Payment\Transaction @@ -366,8 +366,7 @@ public function closeAuthorization($shouldSave = true, $dryRun = false) } /** - * Close a capture transaction - * Logic is similar to closeAuthorization(), but for a capture transaction + * Close a capture transaction. Logic is similar to closeAuthorization(), but for a capture transaction * * @param bool $shouldSave * @return bool|\Magento\Sales\Model\Order\Payment\Transaction @@ -395,6 +394,7 @@ public function closeCapture($shouldSave = true) /** * Check whether authorization in current hierarchy can be voided completely + * * Basically checks whether the authorization exists and it is not affected by a capture or void * * @return bool @@ -472,7 +472,7 @@ public function getAdditionalInformation($key = null) $info = []; } if ($key) { - return isset($info[$key]) ? $info[$key] : null; + return $info[$key] ?? null; } return $info; } @@ -536,6 +536,7 @@ public function close($shouldSave = true) /** * Order ID getter + * * Attempts to get ID from set order payment object, if any, or from data by key 'order_id' * * @return int|null @@ -572,6 +573,7 @@ public function getOrder() /** * Set order instance for transaction depends on transaction behavior + * * If $order equals to true, method isn't loading new order instance. * * @param \Magento\Sales\Model\Order|null|boolean $order @@ -695,7 +697,6 @@ protected function _loadChildren() /** * Check whether this transaction is voided * - * TODO: implement that there should be only one void per authorization * @return bool */ protected function _isVoided() @@ -805,7 +806,6 @@ protected function _verifyTxnId($txnId) /** * Make sure this object is a valid transaction - * TODO for more restriction we can check for data consistency * * @return void * @throws \Magento\Framework\Exception\LocalizedException @@ -833,7 +833,7 @@ public function getTransactionId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setTransactionId($id) { @@ -898,7 +898,7 @@ public function getTxnId() public function getHtmlTxnId() { $this->_eventManager->dispatch($this->_eventPrefix . '_html_txn_id', $this->_getEventData()); - return isset($this->_data['html_txn_id']) ? $this->_data['html_txn_id'] : $this->getTxnId(); + return $this->_data['html_txn_id'] ?? $this->getTxnId(); } /** @@ -942,7 +942,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCreatedAt($createdAt) { @@ -950,7 +950,7 @@ public function setCreatedAt($createdAt) } /** - * {@inheritdoc} + * @inheritdoc */ public function setParentId($id) { @@ -958,7 +958,7 @@ public function setParentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderId($id) { @@ -966,7 +966,7 @@ public function setOrderId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPaymentId($id) { @@ -974,7 +974,7 @@ public function setPaymentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsClosed($isClosed) { @@ -982,7 +982,7 @@ public function setIsClosed($isClosed) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\TransactionExtensionInterface|null */ @@ -992,10 +992,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} - * - * @param \Magento\Sales\Api\Data\TransactionExtensionInterface $extensionAttributes - * @return $this + * @inheritdoc */ public function setExtensionAttributes(\Magento\Sales\Api\Data\TransactionExtensionInterface $extensionAttributes) { diff --git a/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php b/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php new file mode 100644 index 0000000000000..a1015c102b3af --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order\Webapi; + +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn; +use Magento\Sales\Block\Order\Item\Renderer\DefaultRenderer; + +/** + * Class for changing row total in response. + */ +class ChangeOutputArray +{ + /** + * @var DefaultColumn + */ + private $priceRenderer; + + /** + * @var DefaultRenderer + */ + private $defaultRenderer; + + /** + * @param DefaultColumn $priceRenderer + * @param DefaultRenderer $defaultRenderer + */ + public function __construct( + DefaultColumn $priceRenderer, + DefaultRenderer $defaultRenderer + ) { + $this->priceRenderer = $priceRenderer; + $this->defaultRenderer = $defaultRenderer; + } + + /** + * Changing row total for webapi order item response. + * + * @param OrderItemInterface $dataObject + * @param array $result + * @return array + */ + public function execute( + OrderItemInterface $dataObject, + array $result + ): array { + $result[OrderItemInterface::ROW_TOTAL] = $this->priceRenderer->getTotalAmount($dataObject); + $result[OrderItemInterface::BASE_ROW_TOTAL] = $this->priceRenderer->getBaseTotalAmount($dataObject); + $result[OrderItemInterface::ROW_TOTAL_INCL_TAX] = $this->defaultRenderer->getTotalAmount($dataObject); + $result[OrderItemInterface::BASE_ROW_TOTAL_INCL_TAX] = $this->defaultRenderer->getBaseTotalAmount($dataObject); + + return $result; + } +} diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index a98d6193402a9..9a1392fbe9065 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -18,6 +18,9 @@ use Magento\Sales\Model\ResourceModel\Metadata; use Magento\Framework\App\ObjectManager; use Magento\Tax\Api\OrderTaxManagementInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; /** * Repository class @@ -61,6 +64,16 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface */ private $orderTaxManagement; + /** + * @var PaymentAdditionalInfoFactory + */ + private $paymentAdditionalInfoFactory; + + /** + * @var JsonSerializer + */ + private $serializer; + /** * Constructor * @@ -69,13 +82,17 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface * @param CollectionProcessorInterface|null $collectionProcessor * @param \Magento\Sales\Api\Data\OrderExtensionFactory|null $orderExtensionFactory * @param OrderTaxManagementInterface|null $orderTaxManagement + * @param PaymentAdditionalInfoInterfaceFactory|null $paymentAdditionalInfoFactory + * @param JsonSerializer|null $serializer */ public function __construct( Metadata $metadata, SearchResultFactory $searchResultFactory, CollectionProcessorInterface $collectionProcessor = null, \Magento\Sales\Api\Data\OrderExtensionFactory $orderExtensionFactory = null, - OrderTaxManagementInterface $orderTaxManagement = null + OrderTaxManagementInterface $orderTaxManagement = null, + PaymentAdditionalInfoInterfaceFactory $paymentAdditionalInfoFactory = null, + JsonSerializer $serializer = null ) { $this->metadata = $metadata; $this->searchResultFactory = $searchResultFactory; @@ -85,6 +102,10 @@ public function __construct( ->get(\Magento\Sales\Api\Data\OrderExtensionFactory::class); $this->orderTaxManagement = $orderTaxManagement ?: ObjectManager::getInstance() ->get(OrderTaxManagementInterface::class); + $this->paymentAdditionalInfoFactory = $paymentAdditionalInfoFactory ?: ObjectManager::getInstance() + ->get(PaymentAdditionalInfoInterfaceFactory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance() + ->get(JsonSerializer::class); } /** @@ -110,6 +131,7 @@ public function get($id) } $this->setOrderTaxDetails($entity); $this->setShippingAssignments($entity); + $this->setPaymentAdditionalInfo($entity); $this->registry[$id] = $entity; } return $this->registry[$id]; @@ -138,6 +160,34 @@ private function setOrderTaxDetails(OrderInterface $order) $order->setExtensionAttributes($extensionAttributes); } + /** + * Set additional info to the order. + * + * @param OrderInterface $order + * @return void + */ + private function setPaymentAdditionalInfo(OrderInterface $order): void + { + $extensionAttributes = $order->getExtensionAttributes(); + $paymentAdditionalInformation = $order->getPayment()->getAdditionalInformation(); + + $objects = []; + foreach ($paymentAdditionalInformation as $key => $value) { + /** @var PaymentAdditionalInfoInterface $additionalInformationObject */ + $additionalInformationObject = $this->paymentAdditionalInfoFactory->create(); + $additionalInformationObject->setKey($key); + + if (!is_string($value)) { + $value = $this->serializer->serialize($value); + } + $additionalInformationObject->setValue($value); + + $objects[] = $additionalInformationObject; + } + $extensionAttributes->setPaymentAdditionalInfo($objects); + $order->setExtensionAttributes($extensionAttributes); + } + /** * Find entities by criteria * @@ -153,6 +203,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr foreach ($searchResult->getItems() as $order) { $this->setShippingAssignments($order); $this->setOrderTaxDetails($order); + $this->setPaymentAdditionalInfo($order); } return $searchResult; } diff --git a/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php b/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php index 87c5b917f6963..25c15449a9fb4 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php +++ b/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php @@ -103,6 +103,6 @@ protected function getLastUpdatedAtValue($default = '0000-00-00 00:00:00') $row = $this->getConnection()->fetchRow($select); - return isset($row['updated_at']) ? $row['updated_at'] : $default; + return $row['updated_at'] ?? $default; } } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php index 3b127abbda732..2f5a176b9617a 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php @@ -14,7 +14,7 @@ class State { /** - * Check order status before save + * Check order status and adjust the status before save * * @param Order $order * @return $this @@ -23,24 +23,21 @@ class State */ public function check(Order $order) { - if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice() && !$order->canShip()) { - if (0 == $order->getBaseGrandTotal() || $order->canCreditmemo()) { - if ($order->getState() !== Order::STATE_COMPLETE) { - $order->setState(Order::STATE_COMPLETE) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); - } - } elseif ((float)$order->getTotalRefunded() - || !$order->getTotalRefunded() && $order->hasForcedCanCreditmemo() - ) { - if ($order->getState() !== Order::STATE_CLOSED) { - $order->setState(Order::STATE_CLOSED) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); - } - } - } - if ($order->getState() == Order::STATE_NEW && $order->getIsInProcess()) { + $currentState = $order->getState(); + if ($currentState == Order::STATE_NEW && $order->getIsInProcess()) { $order->setState(Order::STATE_PROCESSING) ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)); + $currentState = Order::STATE_PROCESSING; + } + + if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice()) { + if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE]) && !$order->canCreditmemo()) { + $order->setState(Order::STATE_CLOSED) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); + } elseif ($currentState === Order::STATE_PROCESSING && !$order->canShip()) { + $order->setState(Order::STATE_COMPLETE) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); + } } return $this; } diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml index a76fd787ffca1..c814a886a2b33 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml @@ -42,14 +42,14 @@ <!--Admin Fast Create Invoice--> <actionGroup name="adminFastCreateInvoice"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> - <waitForPageLoad stepKey="waitForNewInvoicePageLoad" time="3"/> + <waitForPageLoad stepKey="waitForNewInvoicePageLoad"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> - <waitForPageLoad stepKey="waitForSuccessMessageLoad" time="5"/> + <waitForPageLoad stepKey="waitForSuccessMessageLoad"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage"/> <click selector="{{AdminOrderDetailsOrderViewSection.invoices}}" stepKey="clickInvoices"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask5" /> <click selector="{{AdminOrderInvoicesTabSection.viewInvoice}}" stepKey="openInvoicePage"/> - <waitForPageLoad stepKey="waitForInvoicePageLoad" time="5"/> + <waitForPageLoad stepKey="waitForInvoicePageLoad"/> </actionGroup> <actionGroup name="goToInvoiceIntoOrder"> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index 10d791ddc487d..2bb8b34aa01c8 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -39,17 +39,23 @@ <actionGroup name="navigateToNewOrderPageExistingCustomer"> <arguments> <argument name="customer"/> + <argument name="storeView" defaultValue="_defaultStore"/> </arguments> <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> <waitForPageLoad stepKey="waitForIndexPageLoad"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> <waitForPageLoad stepKey="waitForCustomerGridLoad"/> + <!--Clear grid filters--> + <conditionalClick selector="{{AdminOrderCustomersGridSection.resetButton}}" dependentSelector="{{AdminOrderCustomersGridSection.resetButton}}" visible="true" stepKey="clearExistingCustomerFilters"/> <fillField userInput="{{customer.email}}" selector="{{AdminOrderCustomersGridSection.emailInput}}" stepKey="filterEmail"/> <click selector="{{AdminOrderCustomersGridSection.apply}}" stepKey="applyFilter"/> <waitForPageLoad stepKey="waitForFilteredCustomerGridLoad"/> <click selector="{{AdminOrderCustomersGridSection.firstRow}}" stepKey="clickOnCustomer"/> <waitForPageLoad stepKey="waitForCreateOrderPageLoad" /> + <!-- Select store view if appears --> + <conditionalClick selector="{{AdminOrderStoreScopeTreeSection.storeOption(storeView.name)}}" dependentSelector="{{AdminOrderStoreScopeTreeSection.storeOption(storeView.name)}}" visible="true" stepKey="selectStoreViewIfAppears"/> + <waitForPageLoad stepKey="waitForCreateOrderPageLoadAfterStoreSelect" /> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> </actionGroup> @@ -307,6 +313,32 @@ <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> </actionGroup> + <actionGroup name="CreateOrderInStoreChoosingPaymentMethodActionGroup"> + <arguments> + <argument name="product"/> + <argument name="customer"/> + <argument name="storeView"/> + </arguments> + <amOnPage stepKey="navigateToNewOrderPage" url="{{AdminOrderCreatePage.url}}"/> + <click stepKey="chooseCustomer" selector="{{AdminOrdersGridSection.customerInOrdersSection(customer.firstname)}}"/> + <waitForPageLoad stepKey="waitForStoresPageOpened"/> + <click stepKey="chooseStore" selector="{{AdminOrderStoreScopeTreeSection.storeForOrder(storeView.name)}}"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{OrdersGridSection.addProducts}}" stepKey="clickOnAddProducts"/> + <waitForPageLoad stepKey="waitForProductsListForOrder"/> + <click selector="{{AdminOrdersGridSection.productForOrder(product.sku)}}" stepKey="chooseTheProduct"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addSelectedProductToOrder"/> + <waitForPageLoad stepKey="waitForProductAddedInOrder"/> + <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethods"/> + <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethodsThickened"/> + <waitForElementVisible selector="{{AdminOrderFormPaymentSection.paymentBlock}}" stepKey="waitForPaymentOptions"/> + <conditionalClick selector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" dependentSelector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" visible="true" stepKey="checkCheckMoneyOption"/> + <click selector="{{OrdersGridSection.submitOrder}}" stepKey="submitOrder"/> + <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> + </actionGroup> + <!--Cancel order that is in pending status--> <actionGroup name="cancelPendingOrder"> <click selector="{{AdminOrderDetailsMainActionsSection.cancel}}" stepKey="clickCancelOrder"/> @@ -316,4 +348,10 @@ <see selector="{{AdminMessagesSection.success}}" userInput="You canceled the order." stepKey="seeCancelSuccessMessage"/> <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Canceled" stepKey="seeOrderStatusCanceled"/> </actionGroup> -</actionGroups> \ No newline at end of file + + <!--Select Check Money payment method--> + <actionGroup name="SelectCheckMoneyPaymentMethod"> + <waitForElementVisible selector="{{AdminOrderFormPaymentSection.paymentBlock}}" stepKey="waitForPaymentOptions"/> + <conditionalClick selector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" dependentSelector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" visible="true" stepKey="checkCheckMoneyOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml index fe2fc49f1d015..e48e7bb3f0b1e 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml @@ -30,4 +30,8 @@ <click selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="clickOrderLink"/> <click selector="{{StorefrontCustomerOrderViewSection.printOrderLink}}" stepKey="clickPrintOrderLink"/> </actionGroup> + <actionGroup name="CreateOrderToPrintPageWithSelectedPaymentMethodActionGroup" extends="CreateOrderToPrintPageActionGroup"> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" after="clickNext" stepKey="waitForPaymentSectionLoaded"/> + <conditionalClick selector="{{CheckoutPaymentSection.checkMoneyOrderPayment}}" dependentSelector="{{CheckoutPaymentSection.billingAddress}}" visible="false" before="waitForPlaceOrderButton" stepKey="clickCheckMoneyOrderPayment"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml new file mode 100644 index 0000000000000..c46dd612022fd --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Fill order information fields and click continue--> + <actionGroup name="StorefrontFillOrderInformationActionGroup"> + <arguments> + <argument name="orderId" type="string"/> + <argument name="orderLastName"/> + <argument name="orderEmail"/> + </arguments> + <amOnPage url="{{StorefrontOrdersAndReturnsPage.url}}" stepKey="navigateToOrderAndReturnPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField selector="{{StorefrontOrderAndReturnInformationSection.orderId}}" userInput="{{orderId}}" stepKey="fillOrderId"/> + <fillField selector="{{StorefrontOrderAndReturnInformationSection.bilingLastName}}" userInput="{{orderLastName}}" stepKey="fillBillingLastName"/> + <fillField selector="{{StorefrontOrderAndReturnInformationSection.email}}" userInput="{{orderEmail}}" stepKey="fillEmail"/> + <click selector="{{StorefrontOrderAndReturnInformationSection.continueButton}}" stepKey="clickContinue"/> + <waitForPageLoad stepKey="waitForOrderInformationPageLoad"/> + <seeInCurrentUrl url="{{StorefrontOrderInformationPage.url}}" stepKey="seeOrderInformationUrl"/> + </actionGroup> + + <!--Enter quantity to return and submit--> + <actionGroup name="StorefrontFillQuantityToReturnActionGroup"> + <click selector="{{StorefrontOrderInformationMainSection.return}}" stepKey="gotToCreateNewReturnPage"/> + <waitForPageLoad stepKey="waitForReturnPageLoad"/> + <fillField selector="{{StorefrontCreateNewReturnMainSection.quantityToReturn}}" userInput="1" stepKey="fillQuantityToReturn"/> + <click selector="{{StorefrontCreateNewReturnMainSection.submit}}" stepKey="clickSubmit"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml new file mode 100644 index 0000000000000..76ff20813483e --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableRMA" type="sales_rma_config"> + <requiredEntity type="enabled">EnableRMAStorefront</requiredEntity> + </entity> + <entity name="EnableRMAStorefront" type="enabled"> + <data key="value">1</data> + </entity> + + <entity name="DisableRMA" type="sales_rma_config"> + <requiredEntity type="enabled">DisableRMAStorefront</requiredEntity> + </entity> + <entity name="DisableRMAStorefront" type="enabled"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml new file mode 100644 index 0000000000000..86226265dd146 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="SalesRMAConfig" dataType="sales_rma_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/sales/" method="POST"> + <object key="groups" dataType="sales_rma_config"> + <object key="rma" dataType="sales_rma_config"> + <object key="fields" dataType="sales_rma_config"> + <object key="enabled" dataType="enabled"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml index 333be23dbf346..c8ec12203c676 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml @@ -17,5 +17,6 @@ <section name="AdminOrderFormShippingAddressSection"/> <section name="AdminOrderFormPaymentSection"/> <section name="AdminOrderFormTotalSection"/> + <section name="AdminOrderFormStoreSelectorSection"/> </page> -</pages> \ No newline at end of file +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml new file mode 100644 index 0000000000000..2a14f814eac16 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCreateNewReturnPage" url="rma/guest/create/order_id/" area="guest" module="Magento_Sales"> + <section name="StorefrontCreateNewReturnMainSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderSearchPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderSearchPage.xml new file mode 100644 index 0000000000000..d2a4f4f0459c2 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderSearchPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontGuestOrderSearchPage" url="sales/guest/form/" module="Magento_Sales" area="storefront"> + <section name="StorefrontGuestOrderSearchSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderViewPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderViewPage.xml new file mode 100644 index 0000000000000..69c7fa76129ea --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontGuestOrderViewPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontGuestOrderViewPage" url="sales/guest/view/" module="Magento_Sales" area="storefront"> + <section name="StorefrontGuestOrderViewSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrderInformationPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrderInformationPage.xml new file mode 100644 index 0000000000000..4159f9435c866 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrderInformationPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontOrderInformationPage" url="sales/guest/view" area="guest" module="Magento_Sales"> + <section name="StorefrontOrderInformationMainSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml new file mode 100644 index 0000000000000..32d94c3175807 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontOrdersAndReturnsPage" url="sales/guest/form" area="guest" module="Magento_Sales"> + <section name="OrderAndReturnsMainSection"/> + <section name="OrderInformationSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml index 84fcc8fc47dfb..d65074b3eb1e3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml @@ -22,4 +22,4 @@ <element name="creditMemoItem" type="text" selector="#sales_order_view_tabs_order_creditmemos"/> <element name="viewMemo" type="text" selector="div#sales_order_view_tabs_order_creditmemos_content a.action-menu-item"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml index bc0d1cffd5d3e..92c01cf380746 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml @@ -29,4 +29,4 @@ <element name="totalColumn" type="text" selector=".order-invoice-tables .col-total .price"/> <element name="updateQty" type="button" selector=".order-invoice-tables tfoot button[data-ui-id='order-items-update-button']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml index c02a36432851d..39318a093dde3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml @@ -14,5 +14,6 @@ <element name="resetFilter" type="button" selector=".action-tertiary[title='Reset Filter']"/> <element name="emailInput" type="input" selector="#sales_order_create_customer_grid_filter_email"/> <element name="firstRow" type="button" selector="tr:nth-of-type(1)[data-role='row']"/> + <element name="resetButton" type="button" selector="#sales_order_create_customer_grid [data-action='grid-filter-reset']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index 419c7877246b2..22bff9c286d0f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -14,5 +14,7 @@ <element name="flatRateOption" type="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> <element name="shippingError" type="text" selector="#order[has_shipping]-error"/> <element name="freeShippingOption" type="radio" selector="#s_method_freeshipping_freeshipping" timeout="30"/> + <element name="checkMoneyOption" type="radio" selector="#p_method_checkmo" timeout="30"/> + <element name="paymentBlock" type="text" selector="#order-billing_method" /> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormStoreSelectorSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormStoreSelectorSection.xml new file mode 100644 index 0000000000000..8602c0d3c608f --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormStoreSelectorSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormStoreSelectorSection"> + <element name="storeSelectorContainer" type="input" selector="#order-store-selector"/> + <element name="defaultStoreViewButton" type="radio" selector="//*[contains(@class,'tree-store-scope')]//label[contains(text(),'Default Store View')]"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml new file mode 100644 index 0000000000000..fe8391cf3c28f --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCreateNewReturnMainSection"> + <element name="quantityToReturn" type="input" selector="#items:qty_requested0"/> + <element name="submit" type="submit" selector="//span[contains(text(), 'Submit')]"/> + <element name="resolutionError" type="text" selector="//*[@id='items:resolution0']/following-sibling::div[contains(text(),'Please select an option')]"/> + <element name="conditionError" type="text" selector="//*[@id='items:condition0']/following-sibling::div[contains(text(),'Please select an option')]"/> + <element name="reasonError" type="text" selector="//*[@id='items:reason0']/following-sibling::div[contains(text(),'Please select an option')]"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderSearchSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderSearchSection.xml new file mode 100644 index 0000000000000..5e420ee03bf75 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderSearchSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontGuestOrderSearchSection"> + <element name="orderId" type="input" selector="#oar-order-id"/> + <element name="billingLastName" type="input" selector="#oar-billing-lastname"/> + <element name="findOrderBy" type="select" selector="#quick-search-type-id"/> + <element name="email" type="input" selector="#oar_email"/> + <element name="continue" type="button" selector="//*/span[contains(text(), 'Continue')]"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderViewSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderViewSection.xml new file mode 100644 index 0000000000000..11080761b192c --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontGuestOrderViewSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontGuestOrderViewSection"> + <element name="orderInformationTab" type="text" selector="//*[@class='nav item current']/strong[contains(text(), 'Order Information')]"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderAndReturnInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderAndReturnInformationSection.xml new file mode 100644 index 0000000000000..aa57dd9bc17ba --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderAndReturnInformationSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontOrderAndReturnInformationSection"> + <element name="orderId" type="input" selector="#oar-order-id"/> + <element name="bilingLastName" type="input" selector="#oar-billing-lastname"/> + <element name="findOrderBy" type="select" selector="#quick-search-type-id"/> + <element name="email" type="input" selector="#oar_email"/> + <element name="bilingZipCode" type="input" selector="//input[@id='oar_zip']"/> + <element name="continueButton" type="submit" selector="//button[@title='Continue']"/> + <element name="ordersAndReturnsTitle" type="span" selector="//span[@id='page-title-wrapper']"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderInformationMainSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderInformationMainSection.xml new file mode 100644 index 0000000000000..e42c301206152 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontOrderInformationMainSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontOrderInformationMainSection"> + <element name="orderTitle" type="span" selector="#page-title-wrapper"/> + <element name="return" type="span" selector="//span[contains(text(), 'Return')]"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 24266b5bcfe9f..94e99d25dbb60 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -52,6 +52,8 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml index 0b737ed459f3b..40b26a6b46045 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml @@ -38,7 +38,8 @@ <!--Admin creates order--> <comment userInput="Admin creates order" stepKey="adminCreateOrder"/> <actionGroup ref="navigateToNewOrderPageNewCustomerSingleStore" stepKey="navigateToNewOrderPage"/> - + <conditionalClick selector="{{AdminOrderFormStoreSelectorSection.defaultStoreViewButton}}" dependentSelector="{{AdminOrderFormStoreSelectorSection.storeSelectorContainer}}" visible="true" stepKey="selectFirstStoreViewIfAppears"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappearedAfterStoreSelected"/> <actionGroup ref="addSimpleProductToOrder" stepKey="addSimpleProductToOrder"> <argument name="product" value="SimpleProduct"/> </actionGroup> @@ -55,6 +56,9 @@ <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="SelectCheckMoneyPaymentMethod" stepKey="selectCheckMoneyPayment"/> + <!--Verify totals on Order page--> <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="${{AdminOrderSimpleProduct.subtotal}}" stepKey="seeOrderSubTotal"/> <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="${{AdminOrderSimpleProduct.shipping}}" stepKey="seeOrderShipping"/> @@ -77,4 +81,4 @@ <argument name="product" value="SimpleProduct"/> </actionGroup> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml index ff1e27a2efa08..041252af0ac5b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml @@ -111,6 +111,9 @@ <!--Select FlatRate shipping method--> <actionGroup ref="orderSelectFlatRateShipping" stepKey="orderSelectFlatRateShippingMethod"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="SelectCheckMoneyPaymentMethod" stepKey="selectCheckMoneyPayment"/> + <!--Submit order--> <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> @@ -129,4 +132,4 @@ <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> </after> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml index cc69b6dfb7d41..ed536bd3351f9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml @@ -58,6 +58,9 @@ <!-- Select shipping --> <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="SelectCheckMoneyPaymentMethod" stepKey="selectCheckMoneyPayment"/> + <!--Verify totals on Order page--> <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="${{AdminOrderSimpleProduct.subtotal}}" stepKey="seeOrderSubTotal" after="selectFlatRateShipping"/> <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="${{AdminOrderSimpleProduct.shipping}}" stepKey="seeOrderShipping" after="seeOrderSubTotal"/> @@ -69,4 +72,4 @@ <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> </test> - </tests> \ No newline at end of file + </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml index 08e859b11d1bb..dfbdc53677993 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml @@ -85,6 +85,10 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment3"/> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> @@ -120,6 +124,7 @@ <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage1"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Processing" stepKey="seeOrderProcessing"/> <!--Create Credit Memo--> <comment userInput="Admin creates credit memo" stepKey="createCreditMemoComment"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml index 19bcca985f974..9790b5dfc47f3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -40,7 +40,7 @@ </actionGroup> <!--Create an order at Storefront as Customer 1 --> - <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint"> + <actionGroup ref="CreateOrderToPrintPageWithSelectedPaymentMethodActionGroup" stepKey="createOrderToPrint"> <argument name="Category" value="$$createCategory$$"/> </actionGroup> @@ -64,7 +64,7 @@ </actionGroup> <!--Create an order at Storefront as Customer 2 --> - <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint2"> + <actionGroup ref="CreateOrderToPrintPageWithSelectedPaymentMethodActionGroup" stepKey="createOrderToPrint2"> <argument name="Category" value="$$createCategory$$"/> </actionGroup> 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 38209bb22aef4..759d60d9e6613 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 @@ -6,7 +6,6 @@ namespace Magento\Sales\Test\Unit\Model\Order\Email; -use Magento\Framework\Mail\Template\TransportBuilderByStore; use Magento\Sales\Model\Order\Email\SenderBuilder; class SenderBuilderTest extends \PHPUnit\Framework\TestCase @@ -36,11 +35,6 @@ class SenderBuilderTest extends \PHPUnit\Framework\TestCase */ private $storeMock; - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $transportBuilderByStore; - protected function setUp() { $templateId = 'test_template_id'; @@ -82,11 +76,10 @@ protected function setUp() 'setTemplateIdentifier', 'setTemplateOptions', 'setTemplateVars', + 'setFromByStore', ] ); - $this->transportBuilderByStore = $this->createMock(TransportBuilderByStore::class); - $this->templateContainerMock->expects($this->once()) ->method('getTemplateId') ->will($this->returnValue($templateId)); @@ -109,9 +102,9 @@ protected function setUp() $this->identityContainerMock->expects($this->once()) ->method('getEmailIdentity') ->will($this->returnValue($emailIdentity)); - $this->transportBuilderByStore->expects($this->once()) + $this->transportBuilder->expects($this->once()) ->method('setFromByStore') - ->with($this->equalTo($emailIdentity)); + ->with($this->equalTo($emailIdentity), 1); $this->identityContainerMock->expects($this->once()) ->method('getEmailCopyTo') @@ -120,8 +113,7 @@ protected function setUp() $this->senderBuilder = new SenderBuilder( $this->templateContainerMock, $this->identityContainerMock, - $this->transportBuilder, - $this->transportBuilderByStore + $this->transportBuilder ); } @@ -129,6 +121,8 @@ public function testSend() { $customerName = 'test_name'; $customerEmail = 'test_email'; + $identity = 'email_identity_test'; + $transportMock = $this->createMock( \Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class ); @@ -151,6 +145,9 @@ public function testSend() $this->storeMock->expects($this->once()) ->method('getId') ->willReturn(1); + $this->transportBuilder->expects($this->once()) + ->method('setFromByStore') + ->with($identity, 1); $this->transportBuilder->expects($this->once()) ->method('addTo') ->with($this->equalTo($customerEmail), $this->equalTo($customerName)); @@ -164,6 +161,7 @@ public function testSend() public function testSendCopyTo() { + $identity = 'email_identity_test'; $transportMock = $this->createMock( \Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class ); @@ -177,6 +175,9 @@ public function testSendCopyTo() $this->transportBuilder->expects($this->once()) ->method('addTo') ->with($this->equalTo('example@mail.com')); + $this->transportBuilder->expects($this->once()) + ->method('setFromByStore') + ->with($identity, 1); $this->identityContainerMock->expects($this->once()) ->method('getStore') ->willReturn($this->storeMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index 2e82d8064a9e8..7f0c0639d21f5 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -10,6 +10,7 @@ use Magento\Sales\Api\Data\OrderSearchResultInterfaceFactory as SearchResultFactory; use Magento\Sales\Model\ResourceModel\Metadata; use Magento\Tax\Api\OrderTaxManagementInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -46,6 +47,11 @@ class OrderRepositoryTest extends \PHPUnit\Framework\TestCase */ private $orderTaxManagementMock; + /** + * @var PaymentAdditionalInfoInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentAdditionalInfoFactory; + /** * Setup the test * @@ -69,6 +75,8 @@ protected function setUp() $this->orderTaxManagementMock = $this->getMockBuilder(OrderTaxManagementInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->paymentAdditionalInfoFactory = $this->getMockBuilder(PaymentAdditionalInfoInterfaceFactory::class) + ->disableOriginalConstructor()->setMethods(['create'])->getMockForAbstractClass(); $this->orderRepository = $this->objectManager->getObject( \Magento\Sales\Model\OrderRepository::class, [ @@ -76,7 +84,8 @@ protected function setUp() 'searchResultFactory' => $this->searchResultFactory, 'collectionProcessor' => $this->collectionProcessor, 'orderExtensionFactory' => $orderExtensionFactoryMock, - 'orderTaxManagement' => $this->orderTaxManagementMock + 'orderTaxManagement' => $this->orderTaxManagementMock, + 'paymentAdditionalInfoFactory' => $this->paymentAdditionalInfoFactory ] ); } @@ -95,12 +104,16 @@ public function testGetList() $orderTaxDetailsMock = $this->getMockBuilder(\Magento\Tax\Api\Data\OrderTaxDetailsInterface::class) ->disableOriginalConstructor() ->setMethods(['getAppliedTaxes', 'getItems'])->getMockForAbstractClass(); + $paymentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderPaymentInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $paymentAdditionalInfo = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentAdditionalInfoInterface::class) + ->disableOriginalConstructor()->setMethods(['setKey', 'setValue'])->getMockForAbstractClass(); $extensionAttributes = $this->createPartialMock( \Magento\Sales\Api\Data\OrderExtension::class, [ 'getShippingAssignments', 'setShippingAssignments', 'setConvertingFromQuote', - 'setAppliedTaxes', 'setItemAppliedTaxes' + 'setAppliedTaxes', 'setItemAppliedTaxes', 'setPaymentAdditionalInfo' ] ); $shippingAssignmentBuilder = $this->createMock( @@ -111,6 +124,13 @@ public function testGetList() ->method('process') ->with($searchCriteriaMock, $collectionMock); $itemsMock->expects($this->atLeastOnce())->method('getExtensionAttributes')->willReturn($extensionAttributes); + $itemsMock->expects($this->atleastOnce())->method('getPayment')->willReturn($paymentMock); + $paymentMock->expects($this->atLeastOnce())->method('getAdditionalInformation') + ->willReturn(['method' => 'checkmo']); + $this->paymentAdditionalInfoFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($paymentAdditionalInfo); + $paymentAdditionalInfo->expects($this->atLeastOnce())->method('setKey')->willReturnSelf(); + $paymentAdditionalInfo->expects($this->atLeastOnce())->method('setValue')->willReturnSelf(); $this->orderTaxManagementMock->expects($this->atLeastOnce())->method('getOrderTaxDetails') ->willReturn($orderTaxDetailsMock); $extensionAttributes->expects($this->any()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php index e120d613e323c..d21e575245395 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php @@ -24,7 +24,9 @@ class StateTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->orderMock = $this->createPartialMock(\Magento\Sales\Model\Order::class, [ + $this->orderMock = $this->createPartialMock( + \Magento\Sales\Model\Order::class, + [ '__wakeup', 'getId', 'hasCustomerNoteNotify', @@ -35,13 +37,12 @@ protected function setUp() 'canShip', 'getBaseGrandTotal', 'canCreditmemo', - 'getState', - 'setState', 'getTotalRefunded', 'hasForcedCanCreditmemo', 'getIsInProcess', 'getConfig', - ]); + ] + ); $this->orderMock->expects($this->any()) ->method('getConfig') ->willReturnSelf(); @@ -53,127 +54,88 @@ protected function setUp() } /** - * test check order - order without id - */ - public function testCheckOrderEmpty() - { - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->willReturn(100); - $this->orderMock->expects($this->never()) - ->method('setState'); - - $this->state->check($this->orderMock); - } - - /** - * test check order - set state complete + * @param bool $isCanceled + * @param bool $canUnhold + * @param bool $canInvoice + * @param bool $canShip + * @param int $callCanSkipNum + * @param bool $canCreditmemo + * @param int $callCanCreditmemoNum + * @param string $currentState + * @param string $expectedState + * @param int $callSetStateNum + * @dataProvider stateCheckDataProvider + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ - public function testCheckSetStateComplete() - { + public function testCheck( + bool $canCreditmemo, + int $callCanCreditmemoNum, + bool $canShip, + int $callCanSkipNum, + string $currentState, + string $expectedState = '', + bool $isInProcess = false, + int $callGetIsInProcessNum = 0, + bool $isCanceled = false, + bool $canUnhold = false, + bool $canInvoice = false + ) { + $this->orderMock->setState($currentState); $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canShip') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->will($this->returnValue(100)); - $this->orderMock->expects($this->once()) - ->method('canCreditmemo') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->exactly(2)) - ->method('getState') - ->will($this->returnValue(Order::STATE_PROCESSING)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_COMPLETE) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); - } - - /** - * test check order - set state closed - */ - public function testCheckSetStateClosed() - { + ->willReturn($isCanceled); $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) - ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) + ->willReturn($canUnhold); + $this->orderMock->expects($this->any()) ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) + ->willReturn($canInvoice); + $this->orderMock->expects($this->exactly($callCanSkipNum)) ->method('canShip') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->will($this->returnValue(100)); - $this->orderMock->expects($this->once()) + ->willReturn($canShip); + $this->orderMock->expects($this->exactly($callCanCreditmemoNum)) ->method('canCreditmemo') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->exactly(2)) - ->method('getTotalRefunded') - ->will($this->returnValue(null)); - $this->orderMock->expects($this->once()) - ->method('hasForcedCanCreditmemo') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->exactly(2)) - ->method('getState') - ->will($this->returnValue(Order::STATE_PROCESSING)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_CLOSED) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); + ->willReturn($canCreditmemo); + $this->orderMock->expects($this->exactly($callGetIsInProcessNum)) + ->method('getIsInProcess') + ->willReturn($isInProcess); + $this->state->check($this->orderMock); + $this->assertEquals($expectedState, $this->orderMock->getState()); } - /** - * test check order - set state processing - */ - public function testCheckSetStateProcessing() + public function stateCheckDataProvider() { - $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) - ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canShip') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->once()) - ->method('getState') - ->will($this->returnValue(Order::STATE_NEW)); - $this->orderMock->expects($this->once()) - ->method('getIsInProcess') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_PROCESSING) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); + return [ + 'processing - !canCreditmemo!canShip -> closed' => + [false, 1, false, 0, Order::STATE_PROCESSING, Order::STATE_CLOSED], + 'complete - !canCreditmemo,!canShip -> closed' => + [false, 1, false, 0, Order::STATE_COMPLETE, Order::STATE_CLOSED], + 'processing - !canCreditmemo,canShip -> closed' => + [false, 1, true, 0, Order::STATE_PROCESSING, Order::STATE_CLOSED], + 'complete - !canCreditmemo,canShip -> closed' => + [false, 1, true, 0, Order::STATE_COMPLETE, Order::STATE_CLOSED], + 'processing - canCreditmemo,!canShip -> complete' => + [true, 1, false, 1, Order::STATE_PROCESSING, Order::STATE_COMPLETE], + 'complete - canCreditmemo,!canShip -> complete' => + [true, 1, false, 0, Order::STATE_COMPLETE, Order::STATE_COMPLETE], + 'processing - canCreditmemo, canShip -> processing' => + [true, 1, true, 1, Order::STATE_PROCESSING, Order::STATE_PROCESSING], + 'complete - canCreditmemo, canShip -> complete' => + [true, 1, true, 0, Order::STATE_COMPLETE, Order::STATE_COMPLETE], + 'new - canCreditmemo, canShip, IsInProcess -> processing' => + [true, 1, true, 1, Order::STATE_NEW, Order::STATE_PROCESSING, true, 1], + 'new - canCreditmemo, !canShip, IsInProcess -> processing' => + [true, 1, false, 1, Order::STATE_NEW, Order::STATE_COMPLETE, true, 1], + 'new - canCreditmemo, canShip, !IsInProcess -> new' => + [true, 0, true, 0, Order::STATE_NEW, Order::STATE_NEW, false, 1], + 'hold - canUnhold -> hold' => + [true, 0, true, 0, Order::STATE_HOLDED, Order::STATE_HOLDED, false, 0, false, true], + 'payment_review - canUnhold -> payment_review' => + [true, 0, true, 0, Order::STATE_PAYMENT_REVIEW, Order::STATE_PAYMENT_REVIEW, false, 0, false, true], + 'pending_payment - canUnhold -> pending_payment' => + [true, 0, true, 0, Order::STATE_PENDING_PAYMENT, Order::STATE_PENDING_PAYMENT, false, 0, false, true], + 'cancelled - isCanceled -> cancelled' => + [true, 0, true, 0, Order::STATE_HOLDED, Order::STATE_HOLDED, false, 0, true], + ]; } } diff --git a/app/code/Magento/Sales/ViewModel/Customer/AddressFormatter.php b/app/code/Magento/Sales/ViewModel/Customer/AddressFormatter.php new file mode 100644 index 0000000000000..333ab4066ac80 --- /dev/null +++ b/app/code/Magento/Sales/ViewModel/Customer/AddressFormatter.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\ViewModel\Customer; + +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Customer address formatter + */ +class AddressFormatter implements ArgumentInterface +{ + /** + * Customer form factory + * + * @var \Magento\Customer\Model\Metadata\FormFactory + */ + private $customerFormFactory; + + /** + * Address format helper + * + * @var \Magento\Customer\Helper\Address + */ + private $addressFormatHelper; + + /** + * Directory helper + * + * @var \Magento\Directory\Helper\Data + */ + private $directoryHelper; + + /** + * Session quote + * + * @var \Magento\Backend\Model\Session\Quote + */ + private $session; + + /** + * Json encoder + * + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $jsonEncoder; + + /** + * Customer address + * + * @param \Magento\Customer\Model\Metadata\FormFactory $customerFormFactory + * @param \Magento\Customer\Helper\Address $addressFormatHelper + * @param \Magento\Directory\Helper\Data $directoryHelper + * @param \Magento\Backend\Model\Session\Quote $session + * @param \Magento\Framework\Serialize\Serializer\Json $jsonEncoder + */ + public function __construct( + \Magento\Customer\Model\Metadata\FormFactory $customerFormFactory, + \Magento\Customer\Helper\Address $addressFormatHelper, + \Magento\Directory\Helper\Data $directoryHelper, + \Magento\Backend\Model\Session\Quote $session, + \Magento\Framework\Serialize\Serializer\Json $jsonEncoder + ) { + $this->customerFormFactory = $customerFormFactory; + $this->addressFormatHelper = $addressFormatHelper; + $this->directoryHelper = $directoryHelper; + $this->session = $session; + $this->jsonEncoder = $jsonEncoder; + } + + /** + * Return customer address array as JSON + * + * @param array $addressArray + * + * @return string + */ + public function getAddressesJson(array $addressArray): string + { + $data = $this->getEmptyAddressForm(); + foreach ($addressArray as $addressId => $address) { + $addressForm = $this->customerFormFactory->create( + 'customer_address', + 'adminhtml_customer_address', + $address + ); + $data[$addressId] = $addressForm->outputData( + \Magento\Eav\Model\AttributeDataFactory::OUTPUT_FORMAT_JSON + ); + } + + return $this->jsonEncoder->serialize($data); + } + + /** + * Represent customer address in 'online' format. + * + * @param array $address + * @return string + */ + public function getAddressAsString(array $address): string + { + $formatTypeRenderer = $this->addressFormatHelper->getFormatTypeRenderer('oneline'); + $result = ''; + if ($formatTypeRenderer) { + $result = $formatTypeRenderer->renderArray($address); + } + + return $result; + } + + /** + * Return empty address address form + * + * @return array + */ + private function getEmptyAddressForm(): array + { + $defaultCountryId = $this->directoryHelper->getDefaultCountry($this->session->getStore()); + $emptyAddressForm = $this->customerFormFactory->create( + 'customer_address', + 'adminhtml_customer_address', + [\Magento\Customer\Api\Data\AddressInterface::COUNTRY_ID => $defaultCountryId] + ); + + return [0 => $emptyAddressForm->outputData(\Magento\Eav\Model\AttributeDataFactory::OUTPUT_FORMAT_JSON)]; + } +} diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index da6d2bba552da..d6ea9b7d54861 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -22,119 +22,119 @@ comment="Store Id"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_discount_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Canceled"/> - <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Invoiced"/> - <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Refunded"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="base_shipping_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Canceled"/> - <column xsi:type="decimal" name="base_shipping_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Invoiced"/> - <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Refunded"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_shipping_tax_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Refunded"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="base_subtotal_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Canceled"/> - <column xsi:type="decimal" name="base_subtotal_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Invoiced"/> - <column xsi:type="decimal" name="base_subtotal_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Refunded"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="base_tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Canceled"/> - <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Invoiced"/> - <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Refunded"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="base_total_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Canceled"/> - <column xsi:type="decimal" name="base_total_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Invoiced"/> - <column xsi:type="decimal" name="base_total_invoiced_cost" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_invoiced_cost" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Invoiced Cost"/> - <column xsi:type="decimal" name="base_total_offline_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Offline Refunded"/> - <column xsi:type="decimal" name="base_total_online_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Online Refunded"/> - <column xsi:type="decimal" name="base_total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Paid"/> <column xsi:type="decimal" name="base_total_qty_ordered" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Total Qty Ordered"/> - <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Refunded"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="discount_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Canceled"/> - <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Invoiced"/> - <column xsi:type="decimal" name="discount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Refunded"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="shipping_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Canceled"/> - <column xsi:type="decimal" name="shipping_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Invoiced"/> - <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Refunded"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="shipping_tax_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Refunded"/> <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" comment="Store To Base Rate"/> <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="subtotal_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Canceled"/> - <column xsi:type="decimal" name="subtotal_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Invoiced"/> - <column xsi:type="decimal" name="subtotal_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Refunded"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Canceled"/> - <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Invoiced"/> - <column xsi:type="decimal" name="tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Refunded"/> - <column xsi:type="decimal" name="total_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Canceled"/> - <column xsi:type="decimal" name="total_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Invoiced"/> - <column xsi:type="decimal" name="total_offline_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Offline Refunded"/> - <column xsi:type="decimal" name="total_online_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Online Refunded"/> - <column xsi:type="decimal" name="total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Paid"/> <column xsi:type="decimal" name="total_qty_ordered" scale="4" precision="12" unsigned="false" nullable="true" comment="Total Qty Ordered"/> - <column xsi:type="decimal" name="total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Refunded"/> <column xsi:type="smallint" name="can_ship_partially" padding="5" unsigned="true" nullable="true" identity="false" comment="Can Ship Partially"/> @@ -163,27 +163,27 @@ comment="Quote Id"/> <column xsi:type="int" name="shipping_address_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Shipping Address Id"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Negative"/> - <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Positive"/> - <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Amount"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_total_due" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_due" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Due"/> - <column xsi:type="decimal" name="payment_authorization_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="payment_authorization_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Payment Authorization Amount"/> - <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="total_due" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_due" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Due"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Weight"/> @@ -230,25 +230,25 @@ identity="false" default="0" comment="Total Item Count"/> <column xsi:type="int" name="customer_gender" padding="11" unsigned="false" nullable="true" identity="false" comment="Customer Gender"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="varchar" name="coupon_rule_name" nullable="true" length="255" comment="Coupon Sales Rule Name"/> @@ -304,13 +304,13 @@ <column xsi:type="varchar" name="store_name" nullable="true" length="255" comment="Store Name"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Paid"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Paid"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="varchar" name="base_currency_code" nullable="true" length="3" comment="Base Currency Code"/> @@ -326,13 +326,13 @@ comment="Shipping Method Name"/> <column xsi:type="varchar" name="customer_email" nullable="true" length="255" comment="Customer Email"/> <column xsi:type="varchar" name="customer_group" nullable="true" length="255" comment="Customer Group"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> <column xsi:type="varchar" name="customer_name" nullable="true" length="255" comment="Customer Name"/> <column xsi:type="varchar" name="payment_method" nullable="true" length="255" comment="Payment Method"/> - <column xsi:type="decimal" name="total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -513,78 +513,78 @@ comment="Base Original Price"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Tax Percent"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Invoiced"/> - <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Invoiced"/> <column xsi:type="decimal" name="discount_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Discount Percent"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Invoiced"/> - <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Invoiced"/> - <column xsi:type="decimal" name="amount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Amount Refunded"/> - <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Amount Refunded"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_invoiced" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_invoiced" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Invoiced"/> - <column xsi:type="decimal" name="base_row_invoiced" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_invoiced" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Invoiced"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> - <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Before Discount"/> - <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Before Discount"/> <column xsi:type="varchar" name="ext_order_item_id" nullable="true" length="255" comment="Ext Order Item Id"/> <column xsi:type="smallint" name="locked_do_invoice" padding="5" unsigned="true" nullable="true" identity="false" comment="Locked Do Invoice"/> <column xsi:type="smallint" name="locked_do_ship" padding="5" unsigned="true" nullable="true" identity="false" comment="Locked Do Ship"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Refunded"/> <column xsi:type="decimal" name="tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" comment="Tax Canceled"/> - <column xsi:type="decimal" name="discount_tax_compensation_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Canceled"/> - <column xsi:type="decimal" name="tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Refunded"/> - <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Refunded"/> - <column xsi:type="decimal" name="discount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Refunded"/> - <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="item_id"/> @@ -605,41 +605,41 @@ comment="Entity Id"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent Id"/> - <column xsi:type="decimal" name="base_shipping_captured" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_captured" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Captured"/> - <column xsi:type="decimal" name="shipping_captured" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_captured" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Captured"/> - <column xsi:type="decimal" name="amount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Refunded"/> - <column xsi:type="decimal" name="base_amount_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Paid"/> - <column xsi:type="decimal" name="amount_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Canceled"/> - <column xsi:type="decimal" name="base_amount_authorized" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_authorized" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Authorized"/> - <column xsi:type="decimal" name="base_amount_paid_online" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_paid_online" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Paid Online"/> - <column xsi:type="decimal" name="base_amount_refunded_online" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded_online" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Refunded Online"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="amount_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Paid"/> - <column xsi:type="decimal" name="amount_authorized" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_authorized" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Authorized"/> - <column xsi:type="decimal" name="base_amount_ordered" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount_ordered" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Ordered"/> - <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Refunded"/> - <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Refunded"/> - <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Refunded"/> - <column xsi:type="decimal" name="amount_ordered" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_ordered" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Ordered"/> - <column xsi:type="decimal" name="base_amount_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Canceled"/> <column xsi:type="int" name="quote_payment_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Quote Payment Id"/> @@ -840,9 +840,9 @@ comment="Entity Id"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent Id"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="4" precision="20" unsigned="false" nullable="true" comment="Price"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Weight"/> @@ -929,43 +929,43 @@ comment="Entity Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" comment="Store Id"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Base Rate"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> <column xsi:type="decimal" name="total_qty" scale="4" precision="12" unsigned="false" nullable="true" comment="Total Qty"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> <column xsi:type="int" name="billing_address_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Billing Address Id"/> @@ -994,19 +994,19 @@ comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Refunded"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> @@ -1077,15 +1077,15 @@ <column xsi:type="varchar" name="shipping_address" nullable="true" length="255" comment="Shipping Address"/> <column xsi:type="varchar" name="shipping_information" nullable="true" length="255" comment="Shipping Method Name"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true" comment="Updated At"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1143,11 +1143,11 @@ comment="Base Price"/> <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total"/> <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Discount Amount"/> @@ -1220,53 +1220,53 @@ comment="Entity Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" comment="Store Id"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Negative"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Base Rate"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_adjustment" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_adjustment" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="adjustment" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Positive"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Order Id"/> @@ -1295,17 +1295,17 @@ comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> @@ -1362,7 +1362,7 @@ <column xsi:type="varchar" name="billing_name" nullable="true" length="255" comment="Billing Name"/> <column xsi:type="int" name="state" padding="11" unsigned="false" nullable="true" identity="false" comment="Status"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> <column xsi:type="varchar" name="order_status" nullable="true" length="32" comment="Order Status"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" @@ -1376,15 +1376,15 @@ <column xsi:type="varchar" name="payment_method" nullable="true" length="32" comment="Payment Method"/> <column xsi:type="varchar" name="shipping_information" nullable="true" length="255" comment="Shipping Method Name"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="order_base_grand_total" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="order_base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Order Grand Total"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1593,31 +1593,31 @@ default="0" comment="Total Qty Ordered"/> <column xsi:type="decimal" name="total_qty_invoiced" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Total Qty Invoiced"/> - <column xsi:type="decimal" name="total_income_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_income_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Income Amount"/> - <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Revenue Amount"/> - <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Profit Amount"/> - <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Invoiced Amount"/> - <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Canceled Amount"/> - <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Paid Amount"/> - <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Refunded Amount"/> - <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount"/> - <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount Actual"/> - <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount"/> - <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount Actual"/> - <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount"/> - <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1647,31 +1647,31 @@ default="0" comment="Total Qty Ordered"/> <column xsi:type="decimal" name="total_qty_invoiced" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Total Qty Invoiced"/> - <column xsi:type="decimal" name="total_income_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_income_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Income Amount"/> - <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Revenue Amount"/> - <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Profit Amount"/> - <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Invoiced Amount"/> - <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Canceled Amount"/> - <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Paid Amount"/> - <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Refunded Amount"/> - <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount"/> - <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount Actual"/> - <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount"/> - <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount Actual"/> - <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount"/> - <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1737,11 +1737,11 @@ <column xsi:type="varchar" name="order_status" nullable="false" length="50" comment="Order Status"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Refunded"/> - <column xsi:type="decimal" name="online_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Online Refunded"/> - <column xsi:type="decimal" name="offline_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Offline Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1767,11 +1767,11 @@ <column xsi:type="varchar" name="order_status" nullable="true" length="50" comment="Order Status"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Refunded"/> - <column xsi:type="decimal" name="online_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Online Refunded"/> - <column xsi:type="decimal" name="offline_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Offline Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1798,9 +1798,9 @@ comment="Shipping Description"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="total_shipping" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_shipping" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping"/> - <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1829,9 +1829,9 @@ comment="Shipping Description"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="total_shipping" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_shipping" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping"/> - <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1957,17 +1957,17 @@ <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Title"/> <column xsi:type="decimal" name="percent" scale="4" precision="12" unsigned="false" nullable="true" comment="Percent"/> - <column xsi:type="decimal" name="amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount"/> <column xsi:type="int" name="priority" padding="11" unsigned="false" nullable="false" identity="false" comment="Priority"/> <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" comment="Position"/> - <column xsi:type="decimal" name="base_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount"/> <column xsi:type="smallint" name="process" padding="6" unsigned="false" nullable="false" identity="false" comment="Process"/> - <column xsi:type="decimal" name="base_real_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_real_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Real Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="tax_id"/> @@ -1987,13 +1987,13 @@ comment="Item Id"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="false" comment="Real Tax Percent For Item"/> - <column xsi:type="decimal" name="amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="base_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Base tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="real_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="real_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Real tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="real_base_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="real_base_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Real base tax amount for the item and tax rate"/> <column xsi:type="int" name="associated_item_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Id of the associated item"/> diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index be64ce5c84a35..5a5dd925a3098 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -745,6 +745,10 @@ <virtualType name="ShippingAddressAggregator" type="Magento\Framework\DB\Sql\ConcatExpression"> <arguments> <argument name="columns" xsi:type="array"> + <item name="company" xsi:type="array"> + <item name="tableAlias" xsi:type="string">sales_shipping_address</item> + <item name="columnName" xsi:type="string">company</item> + </item> <item name="street" xsi:type="array"> <item name="tableAlias" xsi:type="string">sales_shipping_address</item> <item name="columnName" xsi:type="string">street</item> @@ -768,6 +772,10 @@ <virtualType name="BillingAddressAggregator" type="Magento\Framework\DB\Sql\ConcatExpression"> <arguments> <argument name="columns" xsi:type="array"> + <item name="company" xsi:type="array"> + <item name="tableAlias" xsi:type="string">sales_billing_address</item> + <item name="columnName" xsi:type="string">company</item> + </item> <item name="street" xsi:type="array"> <item name="tableAlias" xsi:type="string">sales_billing_address</item> <item name="columnName" xsi:type="string">street</item> diff --git a/app/code/Magento/Sales/etc/extension_attributes.xml b/app/code/Magento/Sales/etc/extension_attributes.xml index 7280a1a071548..222f61cdc7324 100644 --- a/app/code/Magento/Sales/etc/extension_attributes.xml +++ b/app/code/Magento/Sales/etc/extension_attributes.xml @@ -10,4 +10,7 @@ <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> <attribute code="shipping_assignments" type="Magento\Sales\Api\Data\ShippingAssignmentInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> + <attribute code="payment_additional_info" type="Magento\Payment\Api\Data\PaymentAdditionalInfoInterface[]" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Sales/etc/webapi_rest/di.xml b/app/code/Magento/Sales/etc/webapi_rest/di.xml index 47fb3f188513c..6435445e0ef93 100644 --- a/app/code/Magento/Sales/etc/webapi_rest/di.xml +++ b/app/code/Magento/Sales/etc/webapi_rest/di.xml @@ -15,4 +15,11 @@ <type name="Magento\Sales\Api\ShipmentRepositoryInterface"> <plugin name="convert_blob_to_string" type="Magento\Sales\Plugin\ShippingLabelConverter" /> </type> + <type name="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="processors" xsi:type="array"> + <item name="\Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Sales/etc/webapi_soap/di.xml b/app/code/Magento/Sales/etc/webapi_soap/di.xml index 47fb3f188513c..6435445e0ef93 100644 --- a/app/code/Magento/Sales/etc/webapi_soap/di.xml +++ b/app/code/Magento/Sales/etc/webapi_soap/di.xml @@ -15,4 +15,11 @@ <type name="Magento\Sales\Api\ShipmentRepositoryInterface"> <plugin name="convert_blob_to_string" type="Magento\Sales\Plugin\ShippingLabelConverter" /> </type> + <type name="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="processors" xsi:type="array"> + <item name="\Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml index c321bee460e46..0f5a3559f3008 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml @@ -80,13 +80,13 @@ <argument name="align" xsi:type="string">center</argument> </arguments> </block> - </block> - <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.customer.grid.columnSet.website_name" as="website_name"> - <arguments> - <argument name="header" xsi:type="string" translate="true">Website</argument> - <argument name="index" xsi:type="string">website_name</argument> - <argument name="align" xsi:type="string">center</argument> - </arguments> + <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.customer.grid.columnSet.website_name" as="website_name"> + <arguments> + <argument name="header" xsi:type="string" translate="true">Website</argument> + <argument name="index" xsi:type="string">website_name</argument> + <argument name="align" xsi:type="string">center</argument> + </arguments> + </block> </block> </block> </referenceBlock> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_index.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_index.xml index eb0a7685e5e22..3832476ff6972 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_index.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_index.xml @@ -45,8 +45,18 @@ <block class="Magento\Sales\Block\Adminhtml\Order\Create\Sidebar\Pviewed" template="Magento_Sales::order/create/sidebar/items.phtml" name="pviewed"/> </block> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Form\Account" template="Magento_Sales::order/create/form/account.phtml" name="form_account"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"/> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Method" template="Magento_Sales::order/create/abstract.phtml" name="shipping_method"> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Method\Form" template="Magento_Sales::order/create/shipping/method/form.phtml" name="order_create_shipping_form" as="form"/> </block> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_billing_address.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_billing_address.xml index 6f0cbdb0cd43f..c52f81d5cb56d 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_billing_address.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_billing_address.xml @@ -8,7 +8,12 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"/> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml index 70b5bfc298274..54348ce961c56 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml @@ -20,8 +20,18 @@ <block class="Magento\Sales\Block\Adminhtml\Order\Create\Sidebar\Pviewed" template="Magento_Sales::order/create/sidebar/items.phtml" name="pviewed"/> </block> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Form\Account" template="Magento_Sales::order/create/form/account.phtml" name="form_account"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"/> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address" template="Magento_Sales::order/create/form/address.phtml" name="billing_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Method" template="Magento_Sales::order/create/abstract.phtml" name="shipping_method"> <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Method\Form" template="Magento_Sales::order/create/shipping/method/form.phtml" name="order.create.shipping.method.form" as="form"/> </block> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_shipping_address.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_shipping_address.xml index 56f6786397df9..559f56dcb845b 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_shipping_address.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_shipping_address.xml @@ -8,7 +8,12 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"/> + <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address"> + <arguments> + <argument name="customerAddressFormatter" xsi:type="object">Magento\Sales\ViewModel\Customer\AddressFormatter</argument> + <argument name="customerAddressCollection" xsi:type="object">Magento\Customer\Model\ResourceModel\Address\Collection</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml index 686e311292ac7..b0a88b8fa37dc 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml @@ -6,6 +6,21 @@ // @codingStandardsIgnoreFile +/** + * @var \Magento\Customer\Model\ResourceModel\Address\Collection $addressCollection + */ +$addressCollection = $block->getData('customerAddressCollection'); + +$addressArray = []; +if ($block->getCustomerId()) { + $addressArray = $addressCollection->setCustomerFilter([$block->getCustomerId()])->toArray(); +} + +/** + * @var \Magento\Sales\ViewModel\Customer\AddressFormatter $customerAddressFormatter + */ +$customerAddressFormatter = $block->getData('customerAddressFormatter'); + /** * @var \Magento\Sales\Block\Adminhtml\Order\Create\Billing\Address|\Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address $block */ @@ -17,7 +32,7 @@ if ($block->getIsShipping()): require(["Magento_Sales/order/create/form"], function(){ order.shippingAddressContainer = '<?= /* @escapeNotVerified */ $_fieldsContainerId ?>'; - order.setAddresses(<?= /* @escapeNotVerified */ $block->getAddressCollectionJson() ?>); + order.setAddresses(<?= /* @escapeNotVerified */ $customerAddressFormatter->getAddressesJson($addressArray) ?>); }); </script> @@ -59,13 +74,11 @@ endif; ?> onchange="order.selectAddress(this, '<?= /* @escapeNotVerified */ $_fieldsContainerId ?>')" class="admin__control-select"> <option value=""><?= /* @escapeNotVerified */ __('Add New Address') ?></option> - <?php foreach ($block->getAddressCollection() as $_address): ?> - <?php //if($block->getAddressAsString($_address)!=$block->getAddressAsString($block->getAddress())): ?> + <?php foreach ($addressArray as $addressId => $address): ?> <option - value="<?= /* @escapeNotVerified */ $_address->getId() ?>"<?php if ($_address->getId() == $block->getAddressId()): ?> selected="selected"<?php endif; ?>> - <?= /* @escapeNotVerified */ $block->getAddressAsString($_address) ?> + value="<?= /* @escapeNotVerified */ $addressId ?>"<?php if ($addressId == $block->getAddressId()): ?> selected="selected"<?php endif; ?>> + <?= /* @escapeNotVerified */ $block->escapeHtml($customerAddressFormatter->getAddressAsString($address)) ?> </option> - <?php //endif; ?> <?php endforeach; ?> </select> </div> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml index fa5ea0568011b..4a77c3b166de9 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml @@ -31,9 +31,9 @@ <?php if ($block->canEditQty()): ?> <tfoot> <tr> - <td colspan="2"> </td> - <td colspan="3"><?= $block->getUpdateButtonHtml() ?></td> <td colspan="3"> </td> + <td><?= $block->getUpdateButtonHtml() ?></td> + <td colspan="4"> </td> </tr> </tfoot> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml b/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml index 9f7146ab084df..f1cd5f2b99865 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml @@ -9,22 +9,23 @@ ?> <?php $_shipment = $block->getShipment() ?> <?php $_order = $block->getOrder() ?> -<?php if ($_shipment && $_order && $_shipment->getAllTracks()): ?> -<br /> -<table class="shipment-track"> - <thead> +<?php $trackCollection = $_order->getTracksCollection($_shipment->getId()) ?> +<?php if ($_shipment && $_order && $trackCollection): ?> + <br /> + <table class="shipment-track"> + <thead> <tr> <th><?= /* @escapeNotVerified */ __('Shipped By') ?></th> <th><?= /* @escapeNotVerified */ __('Tracking Number') ?></th> </tr> - </thead> - <tbody> - <?php foreach ($_shipment->getAllTracks() as $_item): ?> - <tr> - <td><?= $block->escapeHtml($_item->getTitle()) ?>:</td> - <td><?= $block->escapeHtml($_item->getNumber()) ?></td> - </tr> - <?php endforeach ?> - </tbody> -</table> + </thead> + <tbody> + <?php foreach ($trackCollection as $_item): ?> + <tr> + <td><?= $block->escapeHtml($_item->getTitle()) ?>:</td> + <td><?= $block->escapeHtml($_item->getNumber()) ?></td> + </tr> + <?php endforeach ?> + </tbody> + </table> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/guest/form.phtml b/app/code/Magento/Sales/view/frontend/templates/guest/form.phtml index 3ebca4d08b349..89be190588677 100644 --- a/app/code/Magento/Sales/view/frontend/templates/guest/form.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/guest/form.phtml @@ -10,7 +10,7 @@ <form class="form form-orders-search" id="oar-widget-orders-and-returns-form" data-mage-init='{"ordersReturns":{}, "validation":{}}' action="<?= /* @escapeNotVerified */ $block->getActionUrl() ?>" method="post" name="guest_post"> <fieldset class="fieldset"> - <legend class="admin__legend"><span><?= /* @escapeNotVerified */ __('Order Information') ?></span></legend> + <legend class="legend"><span><?= /* @escapeNotVerified */ __('Order Information') ?></span></legend> <br> <div class="field id required"> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/history.phtml b/app/code/Magento/Sales/view/frontend/templates/order/history.phtml index 1c02a5c31ea6b..b9a032212352b 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/history.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/history.phtml @@ -6,6 +6,8 @@ // @codingStandardsIgnoreFile +/** @var \Magento\Sales\Block\Order\History $block */ + ?> <?php $_orders = $block->getOrders(); ?> <?= $block->getChildHtml('info') ?> diff --git a/app/code/Magento/SalesAnalytics/README.md b/app/code/Magento/SalesAnalytics/README.md index 70f456c97d4b3..0b1f804b278fe 100644 --- a/app/code/Magento/SalesAnalytics/README.md +++ b/app/code/Magento/SalesAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_SalesAnalytics module -The Magento_SalesAnalytics module configures data definitions for a data collection related to the Sales module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_SalesAnalytics module configures data definitions for a data collection related to the Sales module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php b/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php index d4cce37fd15fd..1a631886f1a9b 100644 --- a/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php +++ b/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php @@ -38,7 +38,7 @@ public function getById($couponId); * Retrieve a coupon using the specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CouponRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#CouponRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php b/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php index d0d275de63a00..963edf5483e43 100644 --- a/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php +++ b/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php @@ -38,7 +38,7 @@ public function getById($ruleId); * Retrieve sales rules that match te specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#RuleRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#RuleRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php index 12d34b8320d07..da05fd98e609b 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\SalesRule\Model\CouponGenerator; -class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +/** + * Generate promo quote + */ +class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface { /** * @var CouponGenerator diff --git a/app/code/Magento/SalesRule/Model/Coupon.php b/app/code/Magento/SalesRule/Model/Coupon.php index ee1eaff08303c..3c96d08a38dd9 100644 --- a/app/code/Magento/SalesRule/Model/Coupon.php +++ b/app/code/Magento/SalesRule/Model/Coupon.php @@ -39,10 +39,10 @@ protected function _construct() /** * Set rule instance * - * @param \Magento\SalesRule\Model\Rule $rule + * @param Rule $rule * @return $this */ - public function setRule(\Magento\SalesRule\Model\Rule $rule) + public function setRule(Rule $rule) { $this->setRuleId($rule->getId()); return $this; @@ -51,7 +51,7 @@ public function setRule(\Magento\SalesRule\Model\Rule $rule) /** * Load primary coupon for specified rule * - * @param \Magento\SalesRule\Model\Rule|int $rule + * @param Rule|int $rule * @return $this */ public function loadPrimaryByRule($rule) @@ -190,6 +190,8 @@ public function getTimesUsed() } /** + * Set Times Used + * * @param int $timesUsed * @return $this */ @@ -273,6 +275,8 @@ public function getType() } /** + * Set type + * * @param int $type * @return $this */ diff --git a/app/code/Magento/SalesRule/Model/CouponRepository.php b/app/code/Magento/SalesRule/Model/CouponRepository.php index 99d6727a8be29..8a77c3bb0f935 100644 --- a/app/code/Magento/SalesRule/Model/CouponRepository.php +++ b/app/code/Magento/SalesRule/Model/CouponRepository.php @@ -8,7 +8,6 @@ use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; -use Magento\Framework\Api\SortOrder; use Magento\SalesRule\Model\ResourceModel\Coupon\Collection; /** @@ -197,13 +196,13 @@ public function deleteById($couponId) /** * Helper function that adds a FilterGroup to the collection. * - * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup + * @param FilterGroup $filterGroup * @param Collection $collection * @deprecated 100.2.0 * @return void */ protected function addFilterGroupToCollection( - \Magento\Framework\Api\Search\FilterGroup $filterGroup, + FilterGroup $filterGroup, Collection $collection ) { $fields = []; diff --git a/app/code/Magento/SalesRule/Model/Data/Rule.php b/app/code/Magento/SalesRule/Model/Data/Rule.php index 72465b285032e..58520831c016b 100644 --- a/app/code/Magento/SalesRule/Model/Data/Rule.php +++ b/app/code/Magento/SalesRule/Model/Data/Rule.php @@ -16,8 +16,7 @@ * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @codeCoverageIgnore */ -class Rule extends \Magento\Framework\Api\AbstractExtensibleObject implements - \Magento\SalesRule\Api\Data\RuleInterface +class Rule extends \Magento\Framework\Api\AbstractExtensibleObject implements RuleInterface { const KEY_RULE_ID = 'rule_id'; const KEY_NAME = 'name'; @@ -271,6 +270,8 @@ public function getIsAdvanced() } /** + * Set Is Advanced + * * @param bool $isAdvanced * @return $this */ @@ -374,6 +375,8 @@ public function getSortOrder() } /** + * Set Sort Order + * * @param int $sortOrder * @return $this */ @@ -617,7 +620,7 @@ public function setSimpleFreeShipping($simpleFreeShipping) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\SalesRule\Api\Data\RuleExtensionInterface|null */ @@ -627,7 +630,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\SalesRule\Api\Data\RuleExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php index 9705808c88a01..b9d461037a230 100644 --- a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php +++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php @@ -5,10 +5,11 @@ */ namespace Magento\SalesRule\Model\Plugin; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Model\Session; use Magento\SalesRule\Model\ResourceModel\Rule as RuleResource; +/** + * Quote Config Product Attributes Class + */ class QuoteConfigProductAttributes { /** diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Coupon/Usage.php b/app/code/Magento/SalesRule/Model/ResourceModel/Coupon/Usage.php index 6407f04611a36..db32bdbe1e908 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Coupon/Usage.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Coupon/Usage.php @@ -73,11 +73,11 @@ public function loadByCustomerCoupon(\Magento\Framework\DataObject $object, $cus $select = $connection->select()->from( $this->getMainTable() )->where( - 'customer_id =:customet_id' + 'customer_id =:customer_id' )->where( 'coupon_id = :coupon_id' ); - $data = $connection->fetchRow($select, [':coupon_id' => $couponId, ':customet_id' => $customerId]); + $data = $connection->fetchRow($select, [':coupon_id' => $couponId, ':customer_id' => $customerId]); if ($data) { $object->setData($data); } diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php b/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php index 193a949108103..22d8b8446538c 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\ResourceModel; -use Magento\SalesRule\Model\ResourceModel\Rule; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\Operation\AttributeInterface; @@ -37,11 +36,14 @@ public function __construct( } /** + * Read handler + * * @param string $entityType * @param array $entityData * @param array $arguments * @return array * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entityType, $entityData, $arguments = []) { diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php b/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php index 685de1b581fb4..d4ef15f1801bc 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\ResourceModel; -use Magento\SalesRule\Model\ResourceModel\Rule; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\Operation\AttributeInterface; @@ -37,11 +36,14 @@ public function __construct( } /** + * Save handler + * * @param string $entityType * @param array $entityData * @param array $arguments * @return array * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entityType, $entityData, $arguments = []) { diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php index 3cd776fe99f5d..2ae1c1c7ac63a 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php +++ b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\Rule\Action\Discount; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\SalesRule\Model\DeltaPriceRound; use Magento\SalesRule\Model\Validator; @@ -50,6 +49,8 @@ public function __construct( } /** + * Fixed discount for cart calculation + * * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param float $qty diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php index fd5953697c7db..89ec2b84572fc 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php @@ -5,6 +5,9 @@ */ namespace Magento\SalesRule\Model\Rule\Condition; +/** + * Address rule condition data model. + */ class Address extends \Magento\Rule\Model\Condition\AbstractCondition { /** @@ -61,6 +64,7 @@ public function loadAttributeOptions() 'base_subtotal' => __('Subtotal'), 'total_qty' => __('Total Items Quantity'), 'weight' => __('Total Weight'), + 'payment_method' => __('Payment Method'), 'shipping_method' => __('Shipping Method'), 'postcode' => __('Shipping Postcode'), 'region' => __('Shipping Region'), diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php index 9bda4793e8681..ff83bb1ee9129 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php @@ -35,12 +35,13 @@ protected function _addSpecialAttributes(array &$attributes) * * @return string */ - public function getAttribute() + public function getAttribute(): string { $attribute = $this->getData('attribute'); if (strpos($attribute, '::') !== false) { - list (, $attribute) = explode('::', $attribute); + list(, $attribute) = explode('::', $attribute); } + return $attribute; } @@ -53,6 +54,7 @@ public function getAttributeName() if ($this->getAttributeScope()) { $attribute = $this->getAttributeScope() . '::' . $attribute; } + return $this->getAttributeOption($attribute); } @@ -92,6 +94,7 @@ public function getAttributeElementHtml() { $html = parent::getAttributeElementHtml() . $this->getAttributeScopeElement()->getHtml(); + return $html; } @@ -100,7 +103,7 @@ public function getAttributeElementHtml() * * @return \Magento\Framework\Data\Form\Element\AbstractElement */ - private function getAttributeScopeElement() + private function getAttributeScopeElement(): \Magento\Framework\Data\Form\Element\AbstractElement { return $this->getForm()->addField( $this->getPrefix() . '__' . $this->getId() . '__attribute_scope', @@ -110,7 +113,7 @@ private function getAttributeScopeElement() 'value' => $this->getAttributeScope(), 'no_span' => true, 'class' => 'hidden', - 'data-form-part' => $this->getFormName() + 'data-form-part' => $this->getFormName(), ] ); } @@ -119,8 +122,9 @@ private function getAttributeScopeElement() * Set attribute value * * @param string $value + * @return void */ - public function setAttribute($value) + public function setAttribute(string $value) { if (strpos($value, '::') !== false) { list($scope, $attribute) = explode('::', $value); @@ -137,7 +141,8 @@ public function setAttribute($value) public function loadArray($arr) { parent::loadArray($arr); - $this->setAttributeScope(isset($arr['attribute_scope']) ? $arr['attribute_scope'] : null); + $this->setAttributeScope($arr['attribute_scope'] ?? null); + return $this; } @@ -148,6 +153,7 @@ public function asArray(array $arrAttributes = []) { $out = parent::asArray($arrAttributes); $out['attribute_scope'] = $this->getAttributeScope(); + return $out; } @@ -155,7 +161,9 @@ public function asArray(array $arrAttributes = []) * Validate Product Rule Condition * * @param \Magento\Framework\Model\AbstractModel $model + * * @return bool + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function validate(\Magento\Framework\Model\AbstractModel $model) { diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php index 1e8fbf43ec3bc..5b02d3c080938 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php @@ -5,6 +5,9 @@ */ namespace Magento\SalesRule\Model\Rule\Condition\Product; +/** + * Subselect conditions for product. + */ class Subselect extends \Magento\SalesRule\Model\Rule\Condition\Product\Combine { /** @@ -161,7 +164,9 @@ public function validate(\Magento\Framework\Model\AbstractModel $model) } } if ($hasValidChild || parent::validate($item)) { - $total += (($hasValidChild && $useChildrenTotal) ? $childrenAttrTotal : $item->getData($attr)); + $total += ($hasValidChild && $useChildrenTotal) + ? $childrenAttrTotal * $item->getQty() + : $item->getData($attr); } } return $this->validateAttribute($total); diff --git a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php index b57daac1f0d58..fdd6c2b169a7d 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php +++ b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\Rule\Metadata; -use Magento\SalesRule\Model\ResourceModel\Rule\Collection; use Magento\SalesRule\Model\Rule; use Magento\Store\Model\System\Store; use Magento\Customer\Api\GroupRepositoryInterface; diff --git a/app/code/Magento/SalesRule/Model/RuleRepository.php b/app/code/Magento/SalesRule/Model/RuleRepository.php index a2279db85aa1f..2cff0d64dba01 100644 --- a/app/code/Magento/SalesRule/Model/RuleRepository.php +++ b/app/code/Magento/SalesRule/Model/RuleRepository.php @@ -8,7 +8,6 @@ use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; -use Magento\Framework\Api\SortOrder; use Magento\SalesRule\Model\ResourceModel\Rule\Collection; /** @@ -107,7 +106,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\SalesRule\Api\Data\RuleInterface $rule) { @@ -119,7 +118,7 @@ public function save(\Magento\SalesRule\Api\Data\RuleInterface $rule) } /** - * {@inheritdoc} + * @inheritdoc */ public function getById($id) { @@ -136,7 +135,7 @@ public function getById($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria) { @@ -183,13 +182,13 @@ public function deleteById($id) /** * Helper function that adds a FilterGroup to the collection. * - * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup + * @param FilterGroup $filterGroup * @param Collection $collection * @deprecated 100.2.0 * @return void */ protected function addFilterGroupToCollection( - \Magento\Framework\Api\Search\FilterGroup $filterGroup, + FilterGroup $filterGroup, Collection $collection ) { $fields = []; diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php b/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php index 8a8c51e9d349a..e863f2af3354d 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php @@ -6,12 +6,12 @@ namespace Magento\SalesRule\Setup\Patch\Data; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class ConvertSerializedDataToJson + * * @package Magento\SalesRule\Setup\Patch */ class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInterface @@ -59,7 +59,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -69,7 +69,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -77,7 +77,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php b/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php index 625d5769fddf5..22cf4fe6b1075 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php @@ -6,13 +6,13 @@ namespace Magento\SalesRule\Setup\Patch\Data; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\State; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class FillSalesRuleProductAttributeTable + * * @package Magento\SalesRule\Setup\Patch */ class FillSalesRuleProductAttributeTable implements DataPatchInterface, PatchVersionInterface @@ -65,7 +65,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -102,7 +102,7 @@ public function fillSalesRuleProductAttributeTable() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -112,7 +112,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -120,7 +120,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php b/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php index 2387f5f1ed714..cad83b6f5d1dd 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php @@ -7,33 +7,33 @@ namespace Magento\SalesRule\Setup\Patch\Data; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class PrepareRuleModelSerializedData + * * @package Magento\SalesRule\Setup\Patch */ class PrepareRuleModelSerializedData implements DataPatchInterface, PatchVersionInterface { /** - * @var \Magento\Framework\Setup\ModuleDataSetupInterface + * @var ModuleDataSetupInterface */ private $moduleDataSetup; /** * PatchInitial constructor. - * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + * @param ModuleDataSetupInterface $moduleDataSetup */ public function __construct( - \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + ModuleDataSetupInterface $moduleDataSetup ) { $this->moduleDataSetup = $moduleDataSetup; } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -59,7 +59,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -67,7 +67,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -75,7 +75,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml index a794c67e736fc..e5907e1e9c0f5 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml @@ -16,4 +16,26 @@ <!-- This actionGroup was created to be merged from B2B. Retailer Customer Group --> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="Retailer" stepKey="selectRetailerCustomerGroup"/> </actionGroup> + + <!--Set Subtotal condition for Customer Segment--> + <actionGroup name="SetCartAttributeConditionForCartPriceRuleActionGroup"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="operatorType" defaultValue="is" type="string"/> + <argument name="value" type="string"/> + </arguments> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToActionTab"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeaderOpen}}" + visible="false" stepKey="openActionTab"/> + <click selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="applyRuleForConditions"/> + <waitForPageLoad stepKey="waitForDropDownOpened"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.childAttribute}}" userInput="{{attributeName}}" stepKey="selectAttribute"/> + <waitForPageLoad stepKey="waitForOperatorOpened"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('is')}}" stepKey="clickToChooseOption"/> + <selectOption userInput="{{operatorType}}" selector="{{AdminCartPriceRulesFormSection.conditionsOperator}}" stepKey="setOperatorType"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('...')}}" stepKey="clickToChooseOption1"/> + <fillField userInput="{{value}}" selector="{{AdminCartPriceRulesFormSection.conditionsValue}}" stepKey="fillActionValue"/> + <click selector="{{AdminMainActionsSection.saveAndContinue}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml index 6ee7b556beb7d..048dea3f32547 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateCartPriceRuleActionGroup"> <arguments> <argument name="ruleName"/> @@ -24,16 +24,70 @@ <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleWithCouponCode" extends="AdminCreateCartPriceRuleActionGroup"> + <arguments> + <argument name="couponCode" defaultValue="_defaultCoupon.code"/> + </arguments> + <remove keyForRemoval="selectActionType"/> + <remove keyForRemoval="fillDiscountAmount"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="Specific Coupon" stepKey="selectCouponType" after="fillRuleName"/> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.couponCode}}" stepKey="waitForElementVisible" after="selectCouponType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="{{couponCode}}" stepKey="fillCouponCode" after="waitForElementVisible"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCoupon}}" userInput="99" stepKey="fillUserPerCoupon" after="fillCouponCode"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount for whole cart" stepKey="selectActionTypeToFixed" after="clickToExpandActions"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="1" stepKey="fillDiscountAmount" after="selectActionTypeToFixed"/> + </actionGroup> + <!--Delete Cart price Rule for Retailer customer--> <actionGroup name="AdminDeleteCartPriceRuleForRetailerActionGroup"> - <click selector="{{AdminMenuSection.marketing}}" stepKey="clickOnMarketing" /> - <waitForPageLoad stepKey="waitForMarketing" time="3"/> - <click selector="{{CartPriceRulesSubmenuSection.cartPriceRules}}" stepKey="clickOnCartPriceRules"/> - <waitForPageLoad stepKey="waitForCartPriceRules" time="5"/> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="goToCartPriceRules"/> + <waitForPageLoad stepKey="waitForCartPriceRules"/> <fillField selector="{{AdminCartPriceRulesSection.filterByNameInput}}" userInput="{{SimpleSalesRule.name}}" stepKey="filterByName"/> <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="doFilter"/> <click selector="{{AdminCartPriceRulesSection.rowByIndex('1')}}" stepKey="goToEditRulePage"/> <click selector="{{AdminCartPriceRulesFormSection.delete}}" stepKey="clickDeleteButton"/> <click selector="{{AdminCartPriceRulesFormSection.modalAcceptButton}}" stepKey="confirmDelete"/> </actionGroup> + + <actionGroup name="AdminCreateCartPriceRuleWithConditions" extends="AdminCreateCartPriceRuleActionGroup"> + <arguments> + <argument name="condition1" type="string" defaultValue="Products subselection" /> + <argument name="condition2" type="string" defaultValue="Category" /> + <argument name="ruleToChange1" type="string" defaultValue="is" /> + <argument name="rule1" type="string" defaultValue="equals or greater than" /> + <argument name="ruleToChange2" type="string" defaultValue="..." /> + <argument name="rule2" type="string" defaultValue="2" /> + <argument name="categoryName" type="string" defaultValue="_defaultCategory.name" /> + </arguments> + <remove keyForRemoval="fillDiscountAmount" /> + <!--Go to Conditions section--> + <click selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="openConditionsSection" after="selectActionType" /> + <click selector="{{AdminCartPriceRulesFormSection.addCondition('1')}}" stepKey="addFirstCondition" after="openConditionsSection" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleCondition('1')}}" userInput="{{condition1}}" stepKey="selectRule" after="addFirstCondition" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange1)}}" stepKey="waitForFirstRuleElement" after="selectRule" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange1)}}" stepKey="clickToChangeRule" after="waitForFirstRuleElement" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleParameterSelect('1--1')}}" userInput="{{rule1}}" stepKey="selectRule1" after="clickToChangeRule" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="waitForSecondRuleElement" after="selectRule1" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="clickToChangeRule1" after="waitForSecondRuleElement" /> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleParameterInput('1--1')}}" userInput="{{rule2}}" stepKey="fillRule" after="clickToChangeRule1" /> + <click selector="{{AdminCartPriceRulesFormSection.addCondition('1--1')}}" stepKey="addSecondCondition" after="fillRule" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleCondition('1--1')}}" userInput="{{condition2}}" stepKey="selectSecondCondition" after="addSecondCondition" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="waitForThirdRuleElement" after="selectSecondCondition" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="addThirdCondition" after="waitForThirdRuleElement" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.openChooser('1--1--1')}}" stepKey="waitForForthRuleElement" after="addThirdCondition" /> + <click selector="{{AdminCartPriceRulesFormSection.openChooser('1--1--1')}}" stepKey="openChooser" after="waitForForthRuleElement" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.categoryCheckbox(categoryName)}}" stepKey="waitForCategoryVisible" after="openChooser" /> + <checkOption selector="{{AdminCartPriceRulesFormSection.categoryCheckbox(categoryName)}}" stepKey="checkCategoryName" after="waitForCategoryVisible" /> + </actionGroup> + + <actionGroup name="CreateCartPriceRuleSecondWebsiteActionGroup"> + <arguments> + <argument name="ruleName"/> + </arguments> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ruleName.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Second Website" stepKey="selectWebsites"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..35e1bee0952cf --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Search grid with keyword search--> + <actionGroup name="AdminFilterCartPriceRuleActionGroup"> + <arguments> + <argument name="ruleName"/> + </arguments> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <fillField selector="{{AdminCartPriceRulesSection.filterByNameInput}}" userInput="{{ruleName}}" stepKey="filterByName"/> + <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="doFilter"/> + <click selector="{{AdminCartPriceRulesSection.rowByIndex('1')}}" stepKey="goToEditRulePage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml index 70d1fc56d2cea..a196bbd61d0ac 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml @@ -18,6 +18,7 @@ <waitForElementVisible selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" stepKey="waitForCouponField" /> <fillField userInput="{{coupon.code}}" selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" stepKey="fillCouponField"/> <click selector="{{StorefrontSalesRuleCartCouponSection.applyButton}}" stepKey="clickApplyButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> <!-- Cancel Sales Rule Coupon applied to the cart --> @@ -32,11 +33,11 @@ <actionGroup name="StorefrontCheckCouponAppliedActionGroup"> <arguments> <argument name="rule"/> - <argument name="discount"/> + <argument name="discount" type="string"/> </arguments> - <waitForElementVisible selector="{{CheckoutCartSummarySection.discountTotal}}" stepKey="waitForDiscountTotal" /> - <see userInput="{{rule.store_labels[1][store_label]}}" selector="{{CheckoutCartSummarySection.discountLabel}}" stepKey="assertDiscountLabel" /> - <see userInput="-${{discount}}" selector="{{CheckoutCartSummarySection.discountTotal}}" stepKey="assertDiscountTotal" /> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountTotal}}" stepKey="waitForDiscountTotal"/> + <see userInput="{{rule.store_labels[1][store_label]}}" selector="{{CheckoutCartSummarySection.discountLabel}}" stepKey="assertDiscountLabel"/> + <see userInput="-${{discount}}" selector="{{CheckoutCartSummarySection.discountTotal}}" stepKey="assertDiscountTotal"/> </actionGroup> <actionGroup name="VerifyDiscountAmount"> @@ -55,4 +56,4 @@ <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="{{expectedDiscount}}" stepKey="seeDiscountTotal"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml new file mode 100644 index 0000000000000..cc695b347c4fb --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SalesRuleAddressConditions" type="SalesRuleConditionAttribute"> + <data key="subtotal">Magento\SalesRule\Model\Rule\Condition\Address|base_subtotal</data> + <data key="totalItemsQty">Magento\SalesRule\Model\Rule\Condition\Address|total_qty</data> + <data key="totalWeight">Magento\SalesRule\Model\Rule\Condition\Address|weight</data> + <data key="shippingMethod">Magento\SalesRule\Model\Rule\Condition\Address|shipping_method</data> + <data key="shippingPostCode">Magento\SalesRule\Model\Rule\Condition\Address|postcode</data> + <data key="shippingRegion">Magento\SalesRule\Model\Rule\Condition\Address|region</data> + <data key="shippingState">Magento\SalesRule\Model\Rule\Condition\Address|region_id</data> + <data key="shippingCountry">Magento\SalesRule\Model\Rule\Condition\Address|country_id</data> + </entity> +</entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml index 10b198b53f389..3dd87d94d0148 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml @@ -11,5 +11,6 @@ <var key="rule_id" entityKey="rule_id" entityType="SalesRule"/> <data key="code" unique="suffix">Code</data> <data key="is_primary">1</data> + <data key="times_used">0</data> </entity> </entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index 6f9d267fd1fd1..2e841f3dc5fa5 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -91,7 +91,7 @@ </entity> <entity name="SalesRuleSpecificCoupon" type="SalesRule"> <data key="name" unique="suffix">SimpleSalesRule</data> - <data key="description">Sales Rule Descritpion</data> + <data key="description">Sales Rule Description</data> <array key="website_ids"> <item>1</item> </array> @@ -117,4 +117,66 @@ <data key="uses_per_coupon">2</data> <data key="simple_free_shipping">1</data> </entity> + + <entity name="SalesRuleSpecificCouponWithFixedDiscount" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Description</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>1</item> + </array> + <data key="uses_per_customer">10</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">1</data> + <data key="simple_action">cart_fixed</data> + <data key="discount_amount">10</data> + <data key="discount_qty">10</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">0</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">false</data> + <data key="uses_per_coupon">10</data> + <data key="simple_free_shipping">1</data> + </entity> + + <entity name="PriceRuleWithCondition" type="SalesRule"> + <data key="name" unique="suffix">SalesRule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN', 'General', 'Wholesale', 'Retailer'</data> + <data key="apply">Fixed amount discount for whole cart</data> + <data key="discountAmount">50</data> + </entity> + + <entity name="SalesRuleSpecificCouponWithPercentDiscount" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Description</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>1</item> + </array> + <data key="uses_per_customer">10</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">1</data> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + <data key="discount_qty">10</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">0</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">false</data> + <data key="uses_per_coupon">10</data> + <data key="simple_free_shipping">1</data> + </entity> </entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleProductConditionsData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleProductConditionsData.xml new file mode 100644 index 0000000000000..8af7ac0fdd99a --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleProductConditionsData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SalesRuleProductConditions" type="SalesRuleConditionAttribute"> + <data key="priceInCart" >Magento\SalesRule\Model\Rule\Condition\Product|quote_item_price</data> + <data key="quantityInCart">Magento\SalesRule\Model\Rule\Condition\Product|quote_item_qty</data> + <data key="rowTotalInCart">Magento\SalesRule\Model\Rule\Condition\Product|quote_item_row_total</data> + </entity> +</entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRuleEditPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRuleEditPage.xml new file mode 100644 index 0000000000000..faed9d42bcdec --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRuleEditPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCartPriceRuleEditPage" area="admin" url="sales_rule/promo_quote/edit/id/{{salesRuleId}}" module="Magento_SalesRule" parameterized="true"> + <section name="AdminCartPriceRulesFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml index 9ffb703fdf0f6..7628ecf468827 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -27,19 +27,36 @@ <element name="userPerCustomer" type="input" selector="//input[@name='uses_per_customer']"/> <element name="priority" type="input" selector="//*[@name='sort_order']"/> + <!-- Conditions sub-form --> + <element name="conditionsHeader" type="button" selector="div[data-index='conditions']" timeout="30"/> + <element name="conditionsHeaderOpen" type="button" selector="div[data-index='conditions'] div[data-state-collapsible='open']" timeout="30"/> + <element name="conditionsValue" type="input" selector=".rule-param-edit input"/> + <element name="conditionsOperator" type="select" selector=".rule-param-edit select"/> + <element name="addCondition" type="button" selector="//*[@id='conditions__{{arg}}__children']//span" parameterized="true"/> + <element name="ruleCondition" type="select" selector="rule[conditions][{{arg}}][new_child]" parameterized="true"/> + <element name="ruleParameter" type="text" selector="//span[@class='rule-param']/a[contains(text(), '{{arg}}')]" parameterized="true"/> + <element name="ruleParameterSelect" type="select" selector="rule[conditions][{{arg}}][operator]" parameterized="true"/> + <element name="ruleParameterInput" type="input" selector="rule[conditions][{{arg}}][value]" parameterized="true"/> + <element name="openChooser" type="button" selector="//label[@for='conditions__{{arg}}__value']" parameterized="true"/> + <element name="categoryCheckbox" type="checkbox" selector="//span[contains(text(), '{{arg}}')]/parent::a/preceding-sibling::input[@type='checkbox']" parameterized="true"/> + <!-- Actions sub-form --> <element name="actionsTab" type="text" selector="//div[@data-index='actions']//span[contains(.,'Actions')][1]"/> <element name="actionsHeader" type="button" selector="div[data-index='actions']" timeout="30"/> + <element name="actionsHeaderOpen" type="button" selector="div[data-index='actions'] div[data-state-collapsible='open']" timeout="30"/> <element name="apply" type="select" selector="select[name='simple_action']"/> <element name="conditions" type="button" selector=".rule-param.rule-param-new-child > a"/> <element name="childAttribute" type="select" selector="//select[contains(@name, 'new_child')]"/> <element name="condition" type="text" selector="//span[@class='rule-param']/a[text()='{{arg}}']" parameterized="true"/> <element name="operator" type="select" selector="//select[contains(@name, '[operator]')]"/> <element name="option" type="select" selector="//ul[@class='rule-param-children']//select[contains(@name, '[value]')]"/> + <element name="actionValue" type="input" selector=".rule-param-edit input"/> + <element name="actionOperator" type="select" selector=".rule-param-edit select"/> <element name="applyDiscountToShipping" type="checkbox" selector="input[name='apply_to_shipping']"/> <element name="applyDiscountToShippingLabel" type="checkbox" selector="input[name='apply_to_shipping']+label"/> <element name="discountAmount" type="input" selector="input[name='discount_amount']"/> <element name="discountStep" type="input" selector="input[name='discount_step']"/> + <element name="addRewardPoints" type="input" selector="input[name='extension_attributes[reward_points_delta]']"/> <element name="freeShipping" type="select" selector="//select[@name='simple_free_shipping']"/> <!-- Manage Coupon Codes sub-form --> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml index a32c50d9d8617..14d3a734408db 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml @@ -15,5 +15,8 @@ <element name="nameColumns" type="text" selector="td[data-column='name']"/> <element name="rowContainingText" type="text" selector="//*[@id='promo_quote_grid_table']/tbody/tr[td//text()[contains(., '{{var1}}')]]" parameterized="true" timeout="30"/> <element name="rowByIndex" type="text" selector="tr[data-role='row']:nth-of-type({{var1}})" parameterized="true" timeout="30"/> + <element name="rulesRow" type="text" selector="//tr[@data-role='row']"/> + <element name="pageCurrent" type="text" selector="//label[@for='promo_quote_grid_page-current']"/> + <element name="totalCount" type="text" selector="span[data-ui-id*='grid-total-count']"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml index be52aa05f5af1..a51aa0b7a7e66 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml @@ -9,10 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontSalesRuleCartCouponSection"> - <element name="couponHeader" type="button" selector="//*[@id='block-discount-heading']"/> - <element name="couponField" type="text" selector="//*[@id='coupon_code']"/> - <element name="discountBlockActive" type="text" selector="//*[@class='block discount active']" /> - <element name="applyButton" type="text" selector="//*[@id='discount-coupon-form']//button[contains(@class, 'apply')]"/> - <element name="cancelButton" type="text" selector="//*[@id='discount-coupon-form']//button[contains(@class, 'cancel')]"/> + <element name="couponHeader" type="button" selector="#block-discount-heading"/> + <element name="couponField" type="text" selector="#coupon_code"/> + <element name="discountBlockActive" type="text" selector=".block.discount.active" /> + <element name="applyButton" type="text" selector="#discount-coupon-form button[class*='apply']" timeout="30"/> + <element name="cancelButton" type="text" selector="#discount-coupon-form button[class*='cancel']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml new file mode 100644 index 0000000000000..fbcc871a69b97 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCartRulesAppliedForProductInCartTest"> + <annotations> + <features value="SalesRule"/> + <stories value="The cart rule cannot effect the cart"/> + <title value="Check that cart rules applied for product in cart"/> + <description value="Check that cart rules applied for product in cart"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96722"/> + <useCaseId value="MAGETWO-96410"/> + <group value="SalesRule"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create category and product--> + <createData entity="_defaultCategory" stepKey="defaultCategory"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">200</field> + <field key="quantity">500</field> + </createData> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="{{BundleProduct.sku}}"/> + </actionGroup> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCartPriceRule"> + <argument name="ruleName" value="{{PriceRuleWithCondition.name}}"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Start creating a bundle product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <pressKey selector="{{AdminProductFormSection.productSku}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="enter"/> + + <!--Off dynamic price and set value--> + <click selector="{{AdminProductFormBundleSection.dynamicPrice}}" stepKey="offDynamicPrice"/> + <fillField selector="{{AdminProductFormBundleSection.priceField}}" userInput="0" stepKey="setProductPrice"/> + + <!-- Add category to product --> + <click selector="{{AdminProductFormBundleSection.categoriesDropDown}}" stepKey="dropDownCategories"/> + <fillField selector="{{AdminProductFormBundleSection.searchForCategory}}" userInput="$$defaultCategory.name$$" stepKey="searchForCategory"/> + <click selector="{{AdminProductFormBundleSection.selectCategory}}" stepKey="selectCategory"/> + <click selector="{{AdminProductFormBundleSection.categoriesLabel}}" stepKey="clickOnCategoriesLabelToCloseOptions"/> + + <!-- Add option, a "Radio Buttons" type option, with one product and set fixed price 200--> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptionWithOneProduct"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="radio"/> + </actionGroup> + <selectOption selector="{{AdminProductFormBundleSection.bundlePriceType}}" userInput="Fixed" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductFormBundleSection.bundlePriceValue}}" userInput="200" stepKey="fillPriceValue"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Create cart price rule--> + <actionGroup ref="AdminCreateCartPriceRuleWithConditions" stepKey="createRule"> + <argument name="ruleName" value="PriceRuleWithCondition"/> + <argument name="condition1" value="Products subselection"/> + <argument name="condition2" value="Category"/> + <argument name="ruleToChange1" value="is"/> + <argument name="rule1" value="equals or greater than"/> + <argument name="ruleToChange2" value="..."/> + <argument name="rule2" value="2"/> + <argument name="categoryName" value="{{_defaultCategory.name}}"/> + </actionGroup> + + <!--Go to Storefront and add product to cart and checkout from cart--> + <amOnPage url="/$$simpleProduct.name$$.html" stepKey="GoToProduct"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="2" stepKey="setQuantity"/> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="AddProductToCard"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"/> + + <!--Check totals--> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="grabSubtotal"/> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" stepKey="grabShippingTotal"/> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="grabTotal"/> + <assertEquals stepKey="assertSubtotal"> + <expectedResult type="string">$400.00</expectedResult> + <actualResult type="variable">$grabSubtotal</actualResult> + </assertEquals> + <assertEquals stepKey="assertShippingTotal"> + <expectedResult type="string">$10.00</expectedResult> + <actualResult type="variable">$grabShippingTotal</actualResult> + </assertEquals> + <assertEquals stepKey="assertTotal"> + <expectedResult type="string">$410.00</expectedResult> + <actualResult type="variable">$grabTotal</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml index ec835384a52f8..271477070d8cd 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml @@ -75,7 +75,6 @@ <waitForElementVisible selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" stepKey="waitForCouponField" /> <fillField selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" userInput="{$grabCouponCode}" stepKey="fillCouponField"/> <click selector="{{StorefrontSalesRuleCartCouponSection.applyButton}}" stepKey="clickApplyButton"/> - <waitForPageLoad stepKey="waitForProductPageLoad2"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$0.99" stepKey="seeDiscountTotal"/> </test> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml index 6f9474278bf45..ed05f8b27e5ca 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml @@ -86,8 +86,8 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> - <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" - stepKey="waitForPaymentSectionLoaded"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage"/> @@ -126,6 +126,8 @@ <argument name="customerVar" value="CustomerEntityOne"/> <argument name="customerAddressVar" value="CustomerAddressSimple"/> </actionGroup> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage1"/> diff --git a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php index 7812444c68694..ae59a1f90b8e3 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Test\Unit\Block\Adminhtml\Promo\Quote\Edit; -use Magento\SalesRule\Model\RegistryConstants; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class DeleteButtonTest extends \PHPUnit\Framework\TestCase diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php index 0d6c09b7f6141..f7522746b2100 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php @@ -16,7 +16,7 @@ class ReadHandlerTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\ResourceModel\ReadHandler + * @var ReadHandler */ protected $model; @@ -49,7 +49,7 @@ protected function setUp() $this->metadataPool = $this->createMock($className); $this->model = $this->objectManager->getObject( - \Magento\SalesRule\Model\ResourceModel\ReadHandler::class, + ReadHandler::class, [ 'ruleResource' => $this->ruleResource, 'metadataPool' => $this->metadataPool, diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php index 528db1ea0b891..65f345cb78d66 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php @@ -13,7 +13,7 @@ class DateApplierTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier|\PHPUnit_Framework_MockObject_MockObject + * @var DateApplier|\PHPUnit_Framework_MockObject_MockObject */ protected $model; @@ -29,10 +29,7 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->model = $this->objectManager->getObject( - \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier::class, - [] - ); + $this->model = $this->objectManager->getObject(DateApplier::class, []); } /** diff --git a/app/code/Magento/SalesRule/etc/db_schema.xml b/app/code/Magento/SalesRule/etc/db_schema.xml index 8dbdf76387cd8..c7427e49219b5 100644 --- a/app/code/Magento/SalesRule/etc/db_schema.xml +++ b/app/code/Magento/SalesRule/etc/db_schema.xml @@ -208,17 +208,17 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> - <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount Actual"/> <column xsi:type="decimal" name="discount_amount_actual" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount Actual"/> - <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount Actual"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -250,17 +250,17 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> - <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount Actual"/> <column xsi:type="decimal" name="discount_amount_actual" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount Actual"/> - <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount Actual"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -292,11 +292,11 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> diff --git a/app/code/Magento/SalesSequence/Model/Manager.php b/app/code/Magento/SalesSequence/Model/Manager.php index d18a872bf3b62..95b26ba425cf6 100644 --- a/app/code/Magento/SalesSequence/Model/Manager.php +++ b/app/code/Magento/SalesSequence/Model/Manager.php @@ -41,7 +41,9 @@ public function __construct( * * @param string $entityType * @param int $storeId + * * @return \Magento\Framework\DB\Sequence\SequenceInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function getSequence($entityType, $storeId) { diff --git a/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php b/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php index be566b6119bdd..7a4aabea85680 100644 --- a/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php +++ b/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php @@ -96,7 +96,9 @@ public function loadByEntityTypeAndStore($entityType, $storeId) * Using for load sequence profile and setting it into metadata * * @param \Magento\Framework\Model\AbstractModel $object - * @return $this + * + * @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object) { @@ -137,7 +139,12 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) } /** - * @inheritdoc + * Perform actions after object save + * + * @param \Magento\Framework\Model\AbstractModel $object + * + * @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @throws \Magento\Framework\Exception\AlreadyExistsException */ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) { diff --git a/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php b/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php index 368bdf6c26f16..4f1788b0ca353 100644 --- a/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php +++ b/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesSequence\Model\ResourceModel; -use Magento\SalesSequence\Model\Meta as ModelMeta; use Magento\Framework\Model\ResourceModel\Db\Context as DatabaseContext; use Magento\SalesSequence\Model\ProfileFactory; diff --git a/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php b/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php index cc828e7d36a45..cf56c96fb1710 100644 --- a/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php +++ b/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php @@ -49,8 +49,12 @@ public function __construct( } /** + * Observer triggered during adding new store + * * @param EventObserver $observer - * @return $this + * + * @return $this|void + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function execute(EventObserver $observer) { diff --git a/app/code/Magento/SalesSequence/Setup/SequenceCreator.php b/app/code/Magento/SalesSequence/Setup/SequenceCreator.php index b0cbf545957bd..14df80224e2ce 100644 --- a/app/code/Magento/SalesSequence/Setup/SequenceCreator.php +++ b/app/code/Magento/SalesSequence/Setup/SequenceCreator.php @@ -49,6 +49,8 @@ public function __construct( /** * Creates sales sequences. + * + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function create() { diff --git a/app/code/Magento/SalesSequence/etc/db_schema.xml b/app/code/Magento/SalesSequence/etc/db_schema.xml index 0e580b85bd608..7ad48badf7b80 100644 --- a/app/code/Magento/SalesSequence/etc/db_schema.xml +++ b/app/code/Magento/SalesSequence/etc/db_schema.xml @@ -41,7 +41,7 @@ <column xsi:type="varchar" name="entity_type" nullable="false" length="32" comment="Prefix"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Store Id"/> - <column xsi:type="varchar" name="sequence_table" nullable="false" length="32" comment="table for sequence"/> + <column xsi:type="varchar" name="sequence_table" nullable="false" length="64" comment="table for sequence"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="meta_id"/> </constraint> diff --git a/app/code/Magento/SampleData/README.md b/app/code/Magento/SampleData/README.md index 5abcbaab1481f..1077038fc074d 100644 --- a/app/code/Magento/SampleData/README.md +++ b/app/code/Magento/SampleData/README.md @@ -69,4 +69,4 @@ If you have deleted certain entities provided by sample data and want to restore The deleted sample data entities will be restored. Those entities, which were changed, will preserve these changes and will not be restored to the default view. ## Documentation -You can find the more detailed description of sample data manipulation procedures at [http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html](http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html) +You can find the more detailed description of sample data manipulation procedures at [https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html](https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html) diff --git a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php index b4ff445c63f4e..e5e419328eea4 100644 --- a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php +++ b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php @@ -74,6 +74,7 @@ public function getShipment() * Configuration for popup window for packaging * * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getConfigDataJson() { @@ -86,7 +87,7 @@ public function getConfigDataJson() $itemsName = []; $itemsWeight = []; $itemsProductId = []; - + $itemsOrderItemId = []; if ($shipmentId) { $urlParams['shipment_id'] = $shipmentId; $createLabelUrl = $this->getUrl('adminhtml/order_shipment/createLabel', $urlParams); diff --git a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php index be2588dc48711..27047ae46bf1f 100644 --- a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php +++ b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php @@ -184,11 +184,11 @@ public function setActiveFlag($code = 'active') /** * Return code of carrier * - * @return string + * @return string|null */ public function getCarrierCode() { - return isset($this->_code) ? $this->_code : null; + return $this->_code ?? null; } /** @@ -216,6 +216,7 @@ public function getTrackingInfo($tracking) /** * Check if carrier has shipping tracking option available + * * All \Magento\Usa carriers have shipping tracking option available * * @return boolean @@ -399,8 +400,8 @@ protected function _getQuotesCacheKey($requestParams) /** * Checks whether some request to rates have already been done, so we have cache for it - * Used to reduce number of same requests done to carrier service during one session * + * Used to reduce number of same requests done to carrier service during one session * Returns cached response or null * * @param string|array $requestParams @@ -410,7 +411,7 @@ protected function _getCachedQuotes($requestParams) { $key = $this->_getQuotesCacheKey($requestParams); - return isset(self::$_quotesCache[$key]) ? self::$_quotesCache[$key] : null; + return self::$_quotesCache[$key] ?? null; } /** @@ -443,8 +444,7 @@ protected function _prepareServiceName($name) } /** - * Prepare shipment request. - * Validate and correct request information + * Prepare shipment request. Validate and correct request information * * @param \Magento\Framework\DataObject $request * @return void @@ -558,8 +558,7 @@ public function returnOfShipment($request) } /** - * For multi package shipments. Delete requested shipments if the current shipment - * request is failed + * For multi package shipments. Delete requested shipments if the current shipment. Request is failed * * @param array $data * @return bool @@ -625,6 +624,8 @@ public function isGirthAllowed($countyDest = null, $carrierMethodCode = null) } /** + * Set Raw Request + * * @param \Magento\Framework\DataObject|null $request * @return $this * @api @@ -680,6 +681,7 @@ public function parseXml($xmlContent, $customSimplexml = 'SimpleXMLElement') /** * Checks if shipping method can collect rates + * * @return bool */ public function canCollectRates() @@ -689,6 +691,7 @@ public function canCollectRates() /** * Debug errors if showmethod is unset + * * @param Error $errors * * @return void diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/AddressBuilder.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/AddressBuilder.php index f95968d4a1bf7..482f243f6f05d 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/AddressBuilder.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/AddressBuilder.php @@ -41,6 +41,6 @@ private function getStreetLine($number, $street) { $lines = is_array($street) ? $street : []; - return isset($lines[$number - 1]) ? $lines[$number - 1] : ''; + return $lines[$number - 1] ?? ''; } } diff --git a/app/code/Magento/Signifyd/README.md b/app/code/Magento/Signifyd/README.md index 9479972cb21b6..753ace3128d22 100644 --- a/app/code/Magento/Signifyd/README.md +++ b/app/code/Magento/Signifyd/README.md @@ -47,7 +47,7 @@ The following interfaces (marked with the `@api` annotation) provide methods tha - might be used by `Magento\Signifyd\Api\CaseRepositoryInterface` to retrieve a list of case entities by specific conditions -For information about a public API in Magento 2, see [Public interfaces & APIs](http://devdocs.magento.com/guides/v2.1/extension-dev-guide/api-concepts.html). +For information about a public API in Magento 2, see [Public interfaces & APIs](https://devdocs.magento.com/guides/v2.1/extension-dev-guide/api-concepts.html). ## Additional information @@ -67,12 +67,12 @@ The Debug Mode may be enabled in the module configuration. This logs the communi The Magento_Signifyd module does not introduce backward incompatible changes. -You can track [backward incompatible changes in patch releases](http://devdocs.magento.com/guides/v2.0/release-notes/changes/ee_changes.html). +You can track [backward incompatible changes in patch releases](https://devdocs.magento.com/guides/v2.0/release-notes/changes/ee_changes.html). ### Processing supplementary payment information To improve the accuracy of Signifyd's transaction estimation, you may perform these operations (links lead to the Magento Developer Documentation Portal): -- [Provide custom AVS/CVV mapping](http://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#provide-avscvv-response-codes) +- [Provide custom AVS/CVV mapping](https://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#provide-avscvv-response-codes) -- [Retrieve payment method for a placed order](http://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#retrieve-payment-method-for-a-placed-order) +- [Retrieve payment method for a placed order](https://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#retrieve-payment-method-for-a-placed-order) diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index c92d62cd0bbe3..29a1f4a9c666e 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -464,6 +464,7 @@ protected function _getSession() * Validation rules for store * * @return \Zend_Validate_Interface|null + * @throws \Zend_Validate_Exception */ protected function _getValidationRulesBeforeSave() { @@ -489,9 +490,11 @@ protected function _getValidationRulesBeforeSave() /** * Loading store data * - * @param mixed $key - * @param string $field - * @return $this + * @param mixed $key + * @param string $field + * + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function load($key, $field = null) { @@ -566,6 +569,7 @@ public function setWebsite(Website $website) * Retrieve store website * * @return Website|bool + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getWebsite() { @@ -578,9 +582,11 @@ public function getWebsite() /** * Retrieve url using store configuration specific * - * @param string $route - * @param array $params - * @return string + * @param string $route + * @param array $params + * + * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getUrl($route = '', $params = []) { @@ -880,8 +886,10 @@ public function getDefaultCurrency() /** * Set current store currency code * - * @param string $code - * @return string + * @param string $code + * + * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function setCurrentCurrencyCode($code) { @@ -966,6 +974,7 @@ public function getAllowedCurrencies() * Retrieve store current currency * * @return Currency + * @throws \Magento\Framework\Exception\LocalizedException */ public function getCurrentCurrency() { @@ -988,6 +997,7 @@ public function getCurrentCurrency() * Retrieve current currency rate * * @return float + * @throws \Magento\Framework\Exception\LocalizedException */ public function getCurrentCurrencyRate() { @@ -998,6 +1008,7 @@ public function getCurrentCurrencyRate() * Retrieve root category identifier * * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getRootCategoryId() { @@ -1023,6 +1034,7 @@ public function setGroup(Group $group) * Retrieve group model * * @return Group|bool + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getGroup() { @@ -1130,6 +1142,7 @@ public function getDefaultGroupId() * Check if store can be deleted * * @return boolean + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function isCanDelete() { @@ -1145,6 +1158,7 @@ public function isCanDelete() * * @return boolean * @since 100.1.0 + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function isDefault() { @@ -1158,9 +1172,11 @@ public function isDefault() * Retrieve current url for store * * @param bool $fromStore + * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getCurrentUrl($fromStore = true) { @@ -1236,6 +1252,7 @@ public function isActive() * Protect delete from non admin area * * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function beforeDelete() { @@ -1247,6 +1264,7 @@ public function beforeDelete() * Rewrite in order to clear configuration cache * * @return $this + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function afterDelete() { @@ -1306,6 +1324,7 @@ public function isReadOnly($value = null) * Retrieve store group name * * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getFrontendName() { @@ -1338,14 +1357,14 @@ public function getIdentities() } /** - * Get store path + * Return Store Path * * @return string */ public function getStorePath() { $parsedUrl = parse_url($this->getBaseUrl()); - return isset($parsedUrl['path']) ? $parsedUrl['path'] : '/'; + return $parsedUrl['path'] ?? '/'; } /** diff --git a/app/code/Magento/Store/Model/WebsiteRepository.php b/app/code/Magento/Store/Model/WebsiteRepository.php index b58936c32f593..e01bc8191b355 100644 --- a/app/code/Magento/Store/Model/WebsiteRepository.php +++ b/app/code/Magento/Store/Model/WebsiteRepository.php @@ -6,10 +6,10 @@ namespace Magento\Store\Model; +use Magento\Framework\App\Config; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\ResourceModel\Website\CollectionFactory; -use Magento\Framework\App\Config; /** * Information Expert in store websites handling @@ -64,7 +64,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function get($code) { @@ -78,7 +78,14 @@ public function get($code) ]); if ($website->getId() === null) { - throw new NoSuchEntityException(); + throw new NoSuchEntityException( + __( + sprintf( + "The website with code %s that was requested wasn't found. Verify the website and try again.", + $code + ) + ) + ); } $this->entities[$code] = $website; $this->entitiesById[$website->getId()] = $website; @@ -86,7 +93,7 @@ public function get($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function getById($id) { @@ -100,7 +107,14 @@ public function getById($id) ]); if ($website->getId() === null) { - throw new NoSuchEntityException(); + throw new NoSuchEntityException( + __( + sprintf( + "The website with id %s that was requested wasn't found. Verify the website and try again.", + $id + ) + ) + ); } $this->entities[$website->getCode()] = $website; $this->entitiesById[$id] = $website; @@ -108,7 +122,7 @@ public function getById($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function getList() { @@ -127,7 +141,7 @@ public function getList() } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefault() { @@ -150,7 +164,7 @@ public function getDefault() } /** - * {@inheritdoc} + * @inheritdoc */ public function clean() { @@ -176,6 +190,7 @@ private function getAppConfig() /** * Initialize default website. + * * @return void */ private function initDefaultWebsite() diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml index a87356303c6e8..ef8d77c8824ff 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml @@ -22,4 +22,18 @@ <waitForElementVisible selector="{{AdminStoresGridSection.websiteFilterTextField}}" stepKey="waitForStoreGridToReload"/> <see userInput="You saved the website." stepKey="seeSavedMessage" /> </actionGroup> + + <!--Get Website_id--> + <actionGroup name="AdminGetWebsiteIdActionGroup"> + <arguments> + <argument name="website"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnTheStorePage"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters"/> + <fillField selector="{{AdminStoresGridSection.websiteFilterTextField}}" userInput="{{website.name}}" stepKey="fillSearchWebsiteField"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFilters" /> + <see selector="{{AdminStoresGridSection.websiteNameInFirstRow}}" userInput="{{website.name}}" stepKey="verifyThatCorrectWebsiteFound"/> + <click selector="{{AdminStoresGridSection.websiteNameInFirstRow}}" stepKey="clickEditExistingWebsite"/> + <grabFromCurrentUrl regex="~(\d+)/~" stepKey="grabFromCurrentUrl"/> + </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml index 1400fbf12c16c..58fd0a3f0bc2b 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml @@ -23,5 +23,6 @@ <click selector="{{AdminStoresDeleteStoreGroupSection.deleteStoreGroupButton}}" stepKey="clickDeleteWebsiteButton"/> <waitForElementVisible selector="{{AdminStoresGridSection.websiteFilterTextField}}" stepKey="waitForStoreGridToReload"/> <see userInput="You deleted the website." stepKey="seeSavedMessage"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter2"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml similarity index 84% rename from app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml rename to app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml index b21c79692a7cf..023d5fc3587fb 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml @@ -7,17 +7,18 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCreateStoreGroupActionGroup"> + <actionGroup name="AdminStoreGroupCreateActionGroup"> <arguments> <argument name="Website" defaultValue="_defaultWebsite"/> + <argument name="storeGroup"/> </arguments> <amOnPage url="{{AdminSystemStoreGroupPage.url}}" stepKey="navigateToNewStoreGroup"/> <waitForPageLoad stepKey="waitForStoreGroupPageLoad" /> <comment userInput="Creating Store Group" stepKey="storeGroupCreationComment" /> <selectOption selector="{{AdminNewStoreGroupSection.storeGrpWebsiteDropdown}}" userInput="{{Website.name}}" stepKey="selectWebsite" /> - <fillField selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" userInput="{{CustomStoreGroupCustomWebsite.name}}" stepKey="enterStoreGroupName" /> - <fillField selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" userInput="{{CustomStoreGroupCustomWebsite.code}}" stepKey="enterStoreGroupCode" /> + <fillField selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" userInput="{{storeGroup.name}}" stepKey="enterStoreGroupName" /> + <fillField selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" userInput="{{storeGroup.code}}" stepKey="enterStoreGroupCode" /> <selectOption selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" userInput="Default Category" stepKey="setRootCategory" /> <click selector="{{AdminNewStoreGroupActionsSection.saveButton}}" stepKey="clickSaveStoreGroup" /> <waitForElementVisible selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="waitForPageReload"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml index 860f094a48ecc..ac8e9d717fdca 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml @@ -20,4 +20,8 @@ <waitForPageLoad stepKey="waitForStoreViewSwitched"/> <see userInput="{{storeView}}" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewStoreViewName"/> </actionGroup> + <actionGroup name="AdminSwitchToAllStoreViewActionGroup" extends="AdminSwitchStoreViewActionGroup"> + <click selector="{{AdminMainActionsSection.allStoreViews}}" stepKey="clickStoreViewByName" after="waitForStoreViewsAreVisible"/> + <see selector="{{AdminMainActionsSection.storeSwitcher}}" userInput="All Store Views" stepKey="seeNewStoreViewName" after="waitForStoreViewSwitched"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml new file mode 100644 index 0000000000000..cfb2c7e6347c3 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSwitchWebsiteActionGroup"> + <arguments> + <argument name="website"/> + </arguments> + <click selector="{{AdminMainActionsSection.storeViewDropdown}}" stepKey="clickWebsiteSwitchDropdown"/> + <waitForElementVisible selector="{{AdminMainActionsSection.websiteByName('Main Website')}}" stepKey="waitForWebsiteAreVisible"/> + <click selector="{{AdminMainActionsSection.websiteByName(website.name)}}" stepKey="clickWebsiteByName"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitingForInformationModal"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmStoreSwitch"/> + <see userInput="{{website.name}}" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewWebsiteName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml index cfcd25086e067..efd3a8c6b8cad 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml @@ -14,7 +14,7 @@ </arguments> <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="clickStoreViewSwitcher"/> <waitForElementVisible selector="{{StorefrontHeaderSection.storeViewDropdown}}" stepKey="waitForStoreViewDropdown"/> - <click selector="{{StorefrontHeaderSection.storeViewOption(storeView.name)}}" stepKey="clickSelectStoreView"/> + <click selector="{{StorefrontHeaderSection.storeViewOption(storeView.code)}}" stepKey="clickSelectStoreView"/> <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/Data/ProductWebsiteLinkData.xml b/app/code/Magento/Store/Test/Mftf/Data/ProductWebsiteLinkData.xml new file mode 100644 index 0000000000000..8e84b84c8aa49 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/ProductWebsiteLinkData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ProductAssignToWebsite" type="product_website_link"> + <var key="sku" entityKey="sku" entityType="product"/> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 9fae618020ff3..9c418e9ebd496 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -22,8 +22,8 @@ <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreEN" type="store"> - <data key="name">EN</data> - <data key="code">en</data> + <data key="name" unique="suffix">EN</data> + <data key="code" unique="suffix">en</data> <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> @@ -31,8 +31,8 @@ <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreFR" type="store"> - <data key="name">FR</data> - <data key="code">fr</data> + <data key="name" unique="suffix">FR</data> + <data key="code" unique="suffix">fr</data> <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> @@ -65,4 +65,13 @@ <data key="name" unique="suffix">StoreView</data> <data key="code" unique="suffix">StoreViewCode</data> </entity> + <entity name="SecondStoreUnique" type="store"> + <data key="name" unique="suffix">Second Store View </data> + <data key="code" unique="suffix">second_store_view_</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">SecondStoreGroup</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml index 7b31623f237e9..2127b5f3c8b21 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml @@ -21,9 +21,25 @@ <data key="store_action">add</data> <data key="store_type">group</data> </entity> + <entity name="SecondStoreGroup" type="group"> + <data key="group_id">null</data> + <data key="name">Second Store</data> + <data key="code">second_store</data> + <var key="root_category_id" entityKey="id" entityType="category"/> + <data key="store_action">add</data> + <data key="store_type">group</data> + </entity> + <entity name="SecondStoreGroupUnique" type="group"> + <data key="group_id">null</data> + <data key="name" unique="suffix">Second Store </data> + <data key="code" unique="suffix">second_store_</data> + <var key="root_category_id" entityKey="id" entityType="category"/> + <data key="store_action">add</data> + <data key="store_type">group</data> + </entity> <entity name="staticStoreGroup" type="group"> <data key="name">NewStore</data> - <data key="code">Base12</data> + <data key="code" unique="suffix">Base12</data> <data key="root_category_id">2</data> <data key="website_id">1</data> </entity> diff --git a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml index ee137d78d7fd2..f636336524f01 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml @@ -19,4 +19,8 @@ <data key="store_type">website</data> <data key="website_id">null</data> </entity> + <entity name="secondCustomWebsite" extends="customWebsite"> + <data key="name" unique="suffix">Custom Website</data> + <data key="code" unique="suffix">custom_website</data> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/product_website_link-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/product_website_link-meta.xml new file mode 100644 index 0000000000000..ca0725f86c289 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/product_website_link-meta.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="ProductWebsiteLink" dataType="product_website_link" type="create" auth="adminOauth" url="/V1/products/{sku}/websites" method="POST"> + <contentType>application/json</contentType> + <object dataType="product_website_link" key="productWebsiteLink"> + <field key="sku">string</field> + <field key="websiteId">integer</field> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Store/Test/Mftf/Page/StorefrontStoreHomePage.xml b/app/code/Magento/Store/Test/Mftf/Page/StorefrontStoreHomePage.xml new file mode 100644 index 0000000000000..0e6b578882f71 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Page/StorefrontStoreHomePage.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontStoreHomePage" url="/{{store_view}}/" area="storefront" module="Magento_Store" parameterized="true"> + <section name="StorefrontHeaderSection"/> + </page> +</pages> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml index fda182246db4a..14160835af3e1 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml @@ -11,6 +11,8 @@ <section name="AdminMainActionsSection"> <element name="storeSwitcher" type="text" selector=".store-switcher"/> <element name="storeViewDropdown" type="button" selector="#store-change-button"/> - <element name="storeViewByName" type="button" selector="//*[@class='store-switcher-store-view ']/a[contains(text(), '{{storeViewName}}')]" timeout="30" parameterized="true"/> + <element name="storeViewByName" type="button" selector="//*[contains(@class,'store-switcher-store-view')]/*[contains(text(), '{{storeViewName}}')]" timeout="30" parameterized="true"/> + <element name="websiteByName" type="button" selector="//*[@class='store-switcher-website ']/a[contains(text(), '{{websiteName}}')]" timeout="30" parameterized="true"/> + <element name="allStoreViews" type="button" selector=".store-switcher .store-switcher-all" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml index 04cbeb5bc596e..1b84027a5dd4a 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml @@ -16,7 +16,7 @@ <element name="websiteFilterTextField" type="input" selector="#storeGrid_filter_website_title"/> <element name="storeFilterTextField" type="input" selector="#storeGrid_filter_store_title"/> <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]" timeout="30"/> - <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']" timeout="30"/> <element name="websiteNameInFirstRow" type="text" selector=".col-website_title>a"/> <element name="storeGrpNameInFirstRow" type="text" selector=".col-group_title>a"/> <element name="storeNameInFirstRow" type="text" selector=".col-store_title>a"/> diff --git a/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php b/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php index f31acd40a2e69..9b83714166b12 100644 --- a/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php +++ b/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php @@ -80,7 +80,7 @@ public function testBeforeSetRouteParamsScopeInParams() $routeParamsResolverMock->expects($this->once())->method('setScope')->with($storeCode); $routeParamsResolverMock->expects($this->once())->method('getScope')->willReturn($storeCode); - $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam'); + $this->queryParamsResolverMock->expects($this->any())->method('setQueryParam'); $this->model->beforeSetRouteParams( $routeParamsResolverMock, @@ -113,7 +113,7 @@ public function testBeforeSetRouteParamsScopeUseStoreInUrl() $routeParamsResolverMock->expects($this->once())->method('setScope')->with($storeCode); $routeParamsResolverMock->expects($this->once())->method('getScope')->willReturn($storeCode); - $this->queryParamsResolverMock->expects($this->once())->method('setQueryParam')->with('___store', $storeCode); + $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam')->with('___store', $storeCode); $this->model->beforeSetRouteParams( $routeParamsResolverMock, @@ -178,7 +178,7 @@ public function testBeforeSetRouteParamsNoScopeInParams() $routeParamsResolverMock->expects($this->never())->method('setScope'); $routeParamsResolverMock->expects($this->once())->method('getScope')->willReturn(false); - $this->queryParamsResolverMock->expects($this->once())->method('setQueryParam')->with('___store', $storeCode); + $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam')->with('___store', $storeCode); $this->model->beforeSetRouteParams( $routeParamsResolverMock, diff --git a/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php b/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php index 468352af78cbc..9c9d1e6023af0 100644 --- a/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php +++ b/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php @@ -78,7 +78,7 @@ public function beforeSetRouteParams( $storeCode ); - if ($useStoreInUrl && !$this->storeManager->hasSingleStore()) { + if (!$useStoreInUrl && !$this->storeManager->hasSingleStore()) { $this->queryParamsResolver->setQueryParam('___store', $storeCode); } } diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index be005264b7bbf..defe0694d018d 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -236,6 +236,7 @@ <type name="Magento\Framework\App\ScopeResolverPool"> <arguments> <argument name="scopeResolvers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Framework\App\ScopeResolver</item> <item name="store" xsi:type="object">Magento\Store\Model\Resolver\Store</item> <item name="stores" xsi:type="object">Magento\Store\Model\Resolver\Store</item> <item name="group" xsi:type="object">Magento\Store\Model\Resolver\Group</item> diff --git a/app/code/Magento/Store/etc/frontend/di.xml b/app/code/Magento/Store/etc/frontend/di.xml index c39d5df863939..917aedad3d960 100644 --- a/app/code/Magento/Store/etc/frontend/di.xml +++ b/app/code/Magento/Store/etc/frontend/di.xml @@ -9,7 +9,7 @@ <type name="Magento\Framework\App\FrontController"> <plugin name="requestPreprocessor" type="Magento\Store\App\FrontController\Plugin\RequestPreprocessor" sortOrder="50"/> </type> - <type name="Magento\Framework\App\Action\Action"> + <type name="Magento\Framework\App\Action\AbstractAction"> <plugin name="contextPlugin" type="Magento\Store\App\Action\Plugin\Context" sortOrder="10"/> </type> <type name="Magento\Framework\App\RouterList" shared="true"> diff --git a/app/code/Magento/Swatches/Controller/Ajax/Media.php b/app/code/Magento/Swatches/Controller/Ajax/Media.php index 7692245e9b27a..2d20b9a9a4b7e 100644 --- a/app/code/Magento/Swatches/Controller/Ajax/Media.php +++ b/app/code/Magento/Swatches/Controller/Ajax/Media.php @@ -52,6 +52,7 @@ public function __construct( * Get product media for specified configurable product variation * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function execute() { diff --git a/app/code/Magento/Swatches/Helper/Data.php b/app/code/Magento/Swatches/Helper/Data.php index d82109ac12603..69217ea377796 100644 --- a/app/code/Magento/Swatches/Helper/Data.php +++ b/app/code/Magento/Swatches/Helper/Data.php @@ -5,9 +5,9 @@ */ namespace Magento\Swatches\Helper; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; use Magento\Catalog\Api\Data\ProductInterface as Product; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Helper\Image; use Magento\Catalog\Model\Product as ModelProduct; use Magento\Catalog\Model\Product\Image\UrlBuilder; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; @@ -324,35 +324,61 @@ private function addFilterByParent(ProductCollection $productCollection, $parent * @param ModelProduct $product * * @return array + * @throws \Magento\Framework\Exception\LocalizedException */ - public function getProductMediaGallery(ModelProduct $product) + public function getProductMediaGallery(ModelProduct $product): array { $baseImage = null; $gallery = []; $mediaGallery = $product->getMediaGalleryEntries(); + /** @var ProductAttributeMediaGalleryEntryInterface $mediaEntry */ foreach ($mediaGallery as $mediaEntry) { if ($mediaEntry->isDisabled()) { continue; } - - if (in_array('image', $mediaEntry->getTypes(), true) || !$baseImage) { - $baseImage = $mediaEntry->getFile(); + if (!$baseImage || $this->isMainImage($mediaEntry)) { + $baseImage = $mediaEntry; } - $gallery[$mediaEntry->getId()] = $this->getAllSizeImages($mediaEntry->getFile()); + $gallery[$mediaEntry->getId()] = $this->collectImageData($mediaEntry); } if (!$baseImage) { return []; } - $resultGallery = $this->getAllSizeImages($baseImage); + $resultGallery = $this->collectImageData($baseImage); $resultGallery['gallery'] = $gallery; return $resultGallery; } + /** + * Checks if image is main image in gallery + * + * @param ProductAttributeMediaGalleryEntryInterface $mediaEntry + * @return bool + */ + private function isMainImage(ProductAttributeMediaGalleryEntryInterface $mediaEntry): bool + { + return in_array('image', $mediaEntry->getTypes(), true); + } + + /** + * Returns image data for swatches + * + * @param ProductAttributeMediaGalleryEntryInterface $mediaEntry + * @return array + */ + private function collectImageData(ProductAttributeMediaGalleryEntryInterface $mediaEntry): array + { + $image = $this->getAllSizeImages($mediaEntry->getFile()); + $image[ProductAttributeMediaGalleryEntryInterface::POSITION] = $mediaEntry->getPosition(); + $image['isMain'] =$this->isMainImage($mediaEntry); + return $image; + } + /** * Get all size images * diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontDisplayAllCharactersOnTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontDisplayAllCharactersOnTextSwatchTest.xml new file mode 100644 index 0000000000000..470421776cf8f --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontDisplayAllCharactersOnTextSwatchTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDisplayAllCharactersOnTextSwatchTest" extends="StorefrontFilterByTextSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="Create/configure swatches and check the display characters length"/> + <title value="Admin can create product attribute with text swatch and view the display characters in full"/> + <description value="Admin can create product attribute with text swatch and check the display characters in full"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-5975"/> + <group value="Swatches"/> + </annotations> + + <!-- Create swatch #3 --> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch2" after="fillDescription1"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('2')}}" userInput="1234567890123456789012341234" stepKey="fillSwatch2" after="clickAddSwatch2"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('2')}}" userInput="1234567890123456789012341234GreenD" stepKey="fillDescription2" after="fillSwatch2"/> + + <!-- Create swatch #4 --> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch3" after="fillDescription2"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('3')}}" userInput="123456789012345678901" stepKey="fillSwatch3" after="clickAddSwatch3"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('3')}}" userInput="123456789012345678901BrownD" stepKey="fillDescription3" after="fillSwatch3"/> + + <see selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '3')}}" userInput="123456789012345678901" stepKey="seeGreen" after="seeBlue"/> + <see selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '4')}}" userInput="123456789012345678901" stepKey="seeBrown" after="seeGreen"/> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage2"/> + <waitForPageLoad stepKey="waitForCategoryPage2"/> + + <!-- Verify swatch2 is present and shown in full display text characters on storefront in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav2"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute2"/> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '2')}}" stepKey="filterBySwatch2"/> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage3"/> + <waitForPageLoad stepKey="waitForCategoryPage3"/> + + <!-- Verify swatch3 is present and shown in full display text characters on storefront in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav3"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute3"/> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '3')}}" stepKey="filterBySwatch3"/> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage4"/> + <waitForPageLoad stepKey="waitForCategoryPage4"/> + + <!-- Verify swatch4 is present and shown in full display text characters on storefront in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav4"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute4"/> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '4')}}" stepKey="filterBySwatch4"/> + + <!-- Deletes the created configurable product--> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + + </test> + +</tests> diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js index a7ac45176316c..f795f99e8112d 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js @@ -432,7 +432,7 @@ define([ swatchProductAttributes.bindAttributeInputType(); - // @todo: refactor collapsable component + // @todo: refactor collapsible component $('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]') .collapsable() .collapse('hide'); diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 0a8675067ea5d..938028f62502d 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -684,11 +684,21 @@ define([ if (!images) { images = this.options.mediaGalleryInitial; } - - this.updateBaseImage(images, $main, !this.inProductList); + this.updateBaseImage(this._sortImages(images), $main, !this.inProductList); } }, + /** + * Sorting images array + * + * @private + */ + _sortImages: function (images) { + return _.sortBy(images, function (image) { + return image.position; + }); + }, + /** * Event for swatch options * @@ -1233,9 +1243,6 @@ define([ dataMergeStrategy: this.options.gallerySwitchStrategy }); } - - gallery.first(); - } else if (justAnImage && justAnImage.img) { context.find('.product-image-photo').attr('src', justAnImage.img); } diff --git a/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php index d8b251f3c8c91..f841f9c047b82 100644 --- a/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php @@ -27,7 +27,7 @@ public function get($taxClassId); * Retrieve tax classes which match a specific criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxClassRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxClassRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php index 252bc0fc715fc..c0f5ccd95ba98 100644 --- a/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php @@ -47,7 +47,7 @@ public function deleteById($rateId); * Search TaxRates * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRateRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxRateRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php index 1d69f932573bd..5e045d94de45e 100644 --- a/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php @@ -55,7 +55,7 @@ public function deleteById($ruleId); * Search TaxRules * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRuleRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxRuleRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteTaxRuleActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteTaxRuleActionGroup.xml new file mode 100644 index 0000000000000..f6b2b7a4785ab --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminDeleteTaxRuleActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteTaxRule"> + <arguments> + <argument name="taxRuleCode" type="string" /> + </arguments> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleGridPage"/> + <waitForPageLoad stepKey="waitForPageLoad1" /> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{taxRuleCode}}" stepKey="fillTaxRuleCode"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch" /> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow"/> + <waitForPageLoad stepKey="waitForPageLoad" /> + <click selector="{{AdminTaxRuleFormSection.deleteRule}}" stepKey="clickDeleteRule"/> + <click selector="{{AdminTaxRuleFormSection.ok}}" stepKey="clickOk"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml index b3395bd4afaa9..596b72070bf7e 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml @@ -127,19 +127,19 @@ <actionGroup name="changeShippingTaxClass"> <!--Select Configuration menu from Store--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES" /> - <waitForPageLoad stepKey="waitForConfiguration" time="5"/> + <waitForPageLoad stepKey="waitForConfiguration"/> <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations"/> - <waitForPageLoad stepKey="waitForSales" time="5"/> + <waitForPageLoad stepKey="waitForSales"/> <!--Double click the same to fix flaky issue with redirection to Dashboard--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES1" /> - <waitForPageLoad stepKey="waitForConfiguration1" time="5"/> + <waitForPageLoad stepKey="waitForConfiguration1"/> <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations1"/> <waitForPageLoad stepKey="waitForSales1" time="5"/> <!--Change default tax class for Shipping on Taxable Goods--> <click selector="{{ConfigurationListSection.sales}}" stepKey="clickOnSales" /> - <waitForPageLoad stepKey="waitForPaymentMethods" time="5"/> + <waitForPageLoad stepKey="waitForPaymentMethods"/> <click selector="{{AdminConfigureTaxSection.salesTax}}" stepKey="clickOnTax"/> - <waitForPageLoad stepKey="waitForTax" time="5"/> + <waitForPageLoad stepKey="waitForTax"/> <seeInCurrentUrl url="{{AdminTaxConfigurationPage.url}}" stepKey="adminTaxConfiguration"/> <seeElement selector="{{AdminConfigureTaxSection.taxClasses}}" stepKey="taxClassSectionC"/> <click selector="{{AdminConfigureTaxSection.taxClasses}}" stepKey="openTaxClassSection"/> @@ -155,19 +155,19 @@ <actionGroup name="setDefaultShippingTaxClass"> <!--Select Configuration menu from Store--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES" /> - <waitForPageLoad stepKey="waitForConfiguration" time="5"/> + <waitForPageLoad stepKey="waitForConfiguration"/> <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations"/> - <waitForPageLoad stepKey="waitForSales" time="5"/> + <waitForPageLoad stepKey="waitForSales"/> <!--Double click the same to fix flaky issue with redirection to Dashboard--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES1" /> - <waitForPageLoad stepKey="waitForConfiguration1" time="5"/> + <waitForPageLoad stepKey="waitForConfiguration1"/> <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations1"/> - <waitForPageLoad stepKey="waitForSales1" time="5"/> + <waitForPageLoad stepKey="waitForSales1"/> <!--Change default tax class for Shipping on Taxable Goods--> <click selector="{{ConfigurationListSection.sales}}" stepKey="clickOnSales" /> - <waitForPageLoad stepKey="waitForPaymentMethods" time="5"/> + <waitForPageLoad stepKey="waitForPaymentMethods"/> <click selector="{{AdminConfigureTaxSection.salesTax}}" stepKey="clickOnTax"/> - <waitForPageLoad stepKey="waitForTax" time="5"/> + <waitForPageLoad stepKey="waitForTax"/> <seeElement selector="{{AdminConfigureTaxSection.taxClasses}}" stepKey="taxClassSectionC"/> <click selector="{{AdminConfigureTaxSection.taxShippingClassSystem}}" stepKey="checkSystemDefaultValue"/> <click selector="{{AdminConfigureTaxSection.taxClasses}}" stepKey="closeTaxClassSection"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxClassData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxClassData.xml new file mode 100644 index 0000000000000..0edf2d6cac142 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxClassData.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="defaultTaxClass" type="taxClass"> + <data key="class_name" unique="suffix">Tax Class </data> + </entity> + <entity name="customerTaxClass" type="taxClass"> + <data key="class_name" unique="suffix">Customer Tax Class </data> + <data key="class_type">CUSTOMER</data> + </entity> + <entity name="productTaxClass" type="taxClass"> + <data key="class_name" unique="suffix">Product Tax Class </data> + <data key="class_type">PRODUCT</data> + </entity> + <entity name="retailCustomerTaxClass"> + <data key="class_name">Retail Customer</data> + <data key="class_type">CUSTOMER</data> + </entity> + <entity name="taxableGoodsTaxClass"> + <data key="class_name">Taxable Goods</data> + <data key="class_type">PRODUCT</data> + </entity> +</entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml index 666ced6d0ab22..887203a76fdad 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml @@ -18,6 +18,27 @@ <data key="zip_is_range">0</data> <data key="rate">10</data> </entity> + <entity name="US_CA_Rate_1" type="taxRate"> + <data key="id">1</data> + <data key="code">US-CA-*-Rate 1</data> + <data key="tax_country_id">US</data> + <data key="tax_postcode">*</data> + <data key="rate">8.2500</data> + </entity> + <entity name="US_NY_Rate_1" type="taxRate"> + <data key="code">US-NY-*-Rate 1</data> + <data key="tax_country_id">US</data> + <data key="tax_postcode">*</data> + <data key="rate">8.3750</data> + <data key="id">2</data> + </entity> + <entity name="taxRate_US_NY_8_1" type="taxRate"> + <data key="code" unique="suffix">US-NY-*-</data> + <data key="tax_country_id">US</data> + <data key="tax_region_id">43</data> + <data key="tax_postcode">*</data> + <data key="rate">8.1</data> + </entity> <entity name="taxRateCustomRateUS" type="taxRate"> <data key="code" unique="suffix">Tax Rate </data> <data key="tax_country_id">US</data> @@ -39,6 +60,16 @@ <data key="zip_to">96162</data> <data key="rate">15.05</data> </entity> + <entity name="TaxRateWithFixedZipUtah" type="taxRate"> + <data key="code" unique="suffix">Tax Rate </data> + <data key="tax_country_id">US</data> + <data key="tax_country">United States</data> + <data key="tax_region_id">58</data> + <data key="tax_region">Utah</data> + <data key="zip_is_range">0</data> + <data key="tax_postcode">84001</data> + <data key="rate">20</data> + </entity> <entity name="defaultTaxRateWithLargeRate" type="taxRate"> <data key="code" unique="suffix">TaxRate</data> <data key="tax_country_id">GB</data> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml index e11f856c5f57c..fde43cd10e3ea 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml @@ -7,6 +7,20 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="defaultTaxRule" type="taxRule"> + <data key="code" unique="suffix">TaxIdentifier</data> + <data key="position">1</data> + <data key="priority">1</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + </array> + </entity> <entity name="SimpleTaxRule" type="taxRule"> <data key="code" unique="suffix">TaxRule</data> <data key="position">0</data> @@ -23,6 +37,86 @@ </array> <data key="calculate_subtotal">true</data> </entity> + <entity name="taxRuleWithCustomPriorityPosition" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">1</data> + <data key="priority">1</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> + <entity name="taxRuleWithCustomPriority" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">0</data> + <data key="priority">1</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> + <entity name="taxRuleWithCustomPosition" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">1</data> + <data key="priority">0</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> + <entity name="taxRuleWithUpdatePriorityPosition" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">2</data> + <data key="priority">2</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> + <entity name="taxRuleWithCustomTaxClasses" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">0</data> + <data key="priority">0</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> <entity name="TaxRule" type="taxRule"> <data key="name" unique="suffix">TaxName</data> </entity> diff --git a/app/code/Magento/Tax/Test/Mftf/Metadata/tax_class-meta.xml b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_class-meta.xml new file mode 100644 index 0000000000000..c2e5aac8ce6fa --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_class-meta.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateTaxClass" dataType="taxClass" type="create" auth="adminOauth" url="/V1/taxClasses" method="POST" returnRegex="/\d+/"> + <contentType>application/json</contentType> + <object dataType="taxClass" key="taxClass"> + <field key="id">integer</field> + <field key="class_id">integer</field> + <field key="class_name" required="true">string</field> + <field key="class_type">string</field> + </object> + </operation> + <operation name="GetTaxClass" dataType="taxClass" type="get" auth="adminOauth" url="/V1/taxClasses/{return}" method="GET"> + <contentType>application/json</contentType> + </operation> + <operation name="DeleteTaxClass" dataType="taxClass" type="delete" auth="adminOauth" url="/V1/taxClasses/{return}" method="DELETE"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRateGridSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRateGridSection.xml index 057223f4fc546..ba9da182d735e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRateGridSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRateGridSection.xml @@ -16,5 +16,6 @@ <element name="filterByCountry" type="input" selector="#tax_rate_grid_filter_tax_country_id"/> <element name="filterByPostCode" type="input" selector="#tax_rate_grid_filter_tax_postcode"/> <element name="nthRow" type="block" selector="tr[data-role='row']:nth-of-type({{var}})" parameterized="true" timeout="30"/> + <element name="emptyText" type="text" selector=".empty-text"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml index 37a0467089011..c77d3ad0d9444 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml @@ -9,6 +9,21 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminTaxRuleFormSection"> - <element name="taxIdentifier" type="input" selector="input.admin__control-text admin__action-multiselect-search"/> + <element name="fieldTaxRate" type="block" selector="div.field-tax_rate"/> + <element name="taxIdentifier" type="input" selector="input.admin__control-text admin__action-multiselect-search" timeout="30"/> + <element name="code" type="input" selector="#code"/> + <element name="taxRateSearch" type="input" selector="input[data-role='advanced-select-text']"/> + <element name="taxRateSelected" type="input" selector="//span[contains(., '{{taxRateCode}}') and preceding-sibling::input[contains(@class, 'mselect-checked')]]" parameterized="true" /> + <element name="taxRateOption" type="multiselect" selector="//*[@data-ui-id='tax-rate-form-fieldset-element-form-field-tax-rate']//span[.='{{taxRateCode}}']" parameterized="true" /> + <element name="save" type="button" selector="#save" timeout="30"/> + <element name="deleteRule" type="button" selector="#delete" timeout="30"/> + <element name="ok" type="button" selector="button.action-primary.action-accept" timeout="30"/> + <element name="additionalSettings" type="button" selector="#details-summarybase_fieldset" timeout="30"/> + <element name="customerTaxClassOption" type="checkbox" selector="//*[@id='tax_customer_class']/..//span[.='{{taxCustomerClass}}']" parameterized="true"/> + <element name="productTaxClassOption" type="checkbox" selector="//*[@id='tax_product_class']/..//span[.='{{taxProductClass}}']" parameterized="true"/> + <element name="customerTaxClassSelected" type="checkbox" selector="//*[@id='tax_customer_class']/..//span[.='{{taxCustomerClass}}' and preceding-sibling::input[contains(@class, 'mselect-checked')]]" parameterized="true"/> + <element name="productTaxClassSelected" type="checkbox" selector="//*[@id='tax_product_class']/..//span[.='{{taxProductClass}}' and preceding-sibling::input[contains(@class, 'mselect-checked')]]" parameterized="true"/> + <element name="priority" type="text" selector="#priority"/> + <element name="sortOrder" type="text" selector="#position"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleGridSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleGridSection.xml index b76a60cfd6699..dfcaba8329173 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleGridSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleGridSection.xml @@ -10,7 +10,11 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminTaxRuleGridSection"> <element name="add" type="button" selector="#add" timeout="30"/> + <element name="search" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> + <element name="code" type="input" selector="#taxRuleGrid_filter_code"/> + <element name="taxRate" type="input" selector="#taxRuleGrid_filter_tax_rates_codes"/> + <element name="nthRow" type="block" selector="tr[data-role='row']:nth-of-type({{var}})" parameterized="true" timeout="30"/> + <element name="successMessage" type="text" selector="#messages"/> + <element name="emptyText" type="text" selector="td.empty-text"/> </section> </sections> - - diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml new file mode 100644 index 0000000000000..e632f6265f438 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateDefaultsTaxRuleTest"> + <annotations> + <stories value="Create tax rule"/> + <title value="Create tax rule, defaults"/> + <description value="Test log in to Create Tax Rule and Create Defaults Tax Rule"/> + <testCaseId value="MC-5323"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="defaultTaxRate" stepKey="initialTaxRate"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData stepKey="deleteTaxRate" createDataKey="initialTaxRate" /> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <!-- Create a tax rule with defaults --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$initialTaxRate.code$$)}}" stepKey="selectNeededItem" /> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="saveTaxRule" /> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="assertTaxRuleSuccessMessage" /> + + <!-- Verify we see created tax rule with defaults(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch" /> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see created tax rule with defaults on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleField" /> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$initialTaxRate.code$$)}}" stepKey="seeTaxRateSelected" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml new file mode 100644 index 0000000000000..03b12c8f28098 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTaxRuleWithCustomerAndProductTaxClassTest"> + <annotations> + <stories value="Create tax rule"/> + <title value="Create tax rule, with customer and product tax class"/> + <description value="Test log in to Tax Rule and Create tax rule with customer and product tax class"/> + <testCaseId value="MC-5324"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="defaultTaxRate" stepKey="initialTaxRate"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData stepKey="deleteTaxRate" createDataKey="initialTaxRate"/> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + + <!-- Create a tax rule with customer and product class --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$initialTaxRate.code$$)}}" stepKey="clickSelectNeededItem"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_CA_Rate_1.code}}" stepKey="fillTaxRateSearch1"/> + <wait stepKey="waitForSearch2" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_CA_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_NY_Rate_1.code}}" stepKey="fillTaxRateSearch2"/> + <wait stepKey="waitForSearch3" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_NY_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_NY_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem2"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <wait stepKey="waitForAdditionalSettings" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.customerTaxClassOption($$customerTaxClass.class_name$$)}}" stepKey="clickSelectCustomerTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <fillField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithCustomPriorityPosition.priority}}" stepKey="fillPriority"/> + <fillField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithCustomPriorityPosition.position}}" stepKey="fillSortOrder"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see created tax rule with customer and product class(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see created tax rule with customer and product class on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$initialTaxRate.code$$)}}" stepKey="seeTaxRateSelected"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" stepKey="seeSelectNeededItem1"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_NY_Rate_1.code)}}" stepKey="seeSelectNeededItem2"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected($$customerTaxClass.class_name$$)}}" stepKey="seeCustomerTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" stepKey="seeRetailCustomerTaxClass" /> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" stepKey="seeTaxableGoodsTaxClass" /> + <seeInField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithCustomPriorityPosition.priority}}" stepKey="seePriority"/> + <seeInField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithCustomPriorityPosition.position}}" stepKey="seeSortOrder"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml new file mode 100644 index 0000000000000..abff31803a165 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest"> + <annotations> + <stories value="Create tax rule"/> + <title value="Test log in to Create Tax Rule and Create Tax Rule with New and Existing Tax Rate, Customer Tax Class, Product Tax Class"/> + <description value="Test log in to Create tax rule and Create tax rule with New and Existing Tax Rate, Customer Tax Class, Product Tax Class"/> + <testCaseId value="MC-5327"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="taxRate_US_NY_8_1" stepKey="TaxRateWithCustomRate"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData stepKey="deleteTaxRate" createDataKey="TaxRateWithCustomRate"/> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + + <!-- Create a tax rule with new and existing tax rate, customer tax class, product tax class --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$TaxRateWithCustomRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$TaxRateWithCustomRate.code$$)}}" stepKey="clickSelectNeededItem"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_CA_Rate_1.code}}" stepKey="fillTaxRateSearch1"/> + <wait stepKey="waitForSearch2" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_CA_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem1"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.customerTaxClassOption($$customerTaxClass.class_name$$)}}" stepKey="clickSelectCustomerTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see created tax rule with new and existing tax rate, customer tax class, product tax class(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see created tax rule with new and existing tax rate, customer tax class, product tax class on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$TaxRateWithCustomRate.code$$" stepKey="fillTaxRateSearch3"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$TaxRateWithCustomRate.code$$)}}" stepKey="seeTaxRateSelected"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_CA_Rate_1.code}}" stepKey="fillTaxRateSearch4"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" stepKey="seeSelectNeededItem1"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected($$customerTaxClass.class_name$$)}}" stepKey="seeCustomerTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" stepKey="seeRetailCustomerTaxClass" /> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" stepKey="seeTaxableGoodsTaxClass" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml new file mode 100644 index 0000000000000..6e9b81743ddf0 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest"> + <annotations> + <stories value="Create tax rule"/> + <title value="Creating tax rule with new tax classes and tax rate"/> + <description value="Test log in to Create Tax Rule and Create tax rule with New Tax Classes and Tax Rate"/> + <testCaseId value="MC-5325"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="defaultTaxRate" stepKey="initialTaxRate"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData stepKey="deleteTaxRate" createDataKey="initialTaxRate"/> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + + <!-- Create a tax rule with new tax classes and tax rate --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$initialTaxRate.code$$)}}" stepKey="clickSelectNeededItem"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_CA_Rate_1.code}}" stepKey="fillTaxRateSearch1"/> + <wait stepKey="waitForSearch2" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_CA_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_NY_Rate_1.code}}" stepKey="fillTaxRateSearch2"/> + <wait stepKey="waitForSearch3" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_NY_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_NY_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem2"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.customerTaxClassOption($$customerTaxClass.class_name$$)}}" stepKey="clickSelectCustomerTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <fillField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithCustomPosition.position}}" stepKey="fillSortOrder"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see created tax rule with new tax classes and tax rate(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see created tax rule with new tax classes and tax rate on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$initialTaxRate.code$$)}}" stepKey="seeTaxRateSelected"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" stepKey="seeSelectNeededItem1"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_NY_Rate_1.code)}}" stepKey="seeSelectNeededItem2"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected($$customerTaxClass.class_name$$)}}" stepKey="seeCustomerTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" stepKey="seeRetailCustomerTaxClass" /> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" stepKey="seeTaxableGoodsTaxClass" /> + <seeInField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithCustomPosition.position}}" stepKey="seeSortOrder"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml new file mode 100644 index 0000000000000..6a9bf30811ff5 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTaxRuleWithZipRangeTest"> + <annotations> + <stories value="Create tax rule"/> + <title value="Create tax rule, with zip range"/> + <description value="Test log in to Create Tax Rule and Create tax rule with Zip Range"/> + <testCaseId value="MC-5326"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="defaultTaxRateWithZipRange" stepKey="taxRateWithZipRange"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData stepKey="deleteTaxRate" createDataKey="taxRateWithZipRange"/> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + + <!-- Create a tax rule with new tax classes and tax rate --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$taxRateWithZipRange.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$taxRateWithZipRange.code$$)}}" stepKey="clickSelectNeededItem"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{US_CA_Rate_1.code}}" stepKey="fillTaxRateSearch1"/> + <wait stepKey="waitForSearch2" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.taxRateOption(US_CA_Rate_1.code)}}" dependentSelector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" visible="false" stepKey="clickSelectNeededItem1"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <wait stepKey="waitForAdditionalSettings" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.customerTaxClassOption($$customerTaxClass.class_name$$)}}" stepKey="clickSelectCustomerTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <fillField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithCustomPriority.priority}}" stepKey="fillPriority"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see created tax rule with zip range(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see created tax rule with zip range on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$taxRateWithZipRange.code$$)}}" stepKey="seeTaxRateSelected"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected(US_CA_Rate_1.code)}}" stepKey="seeSelectNeededItem1"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected($$customerTaxClass.class_name$$)}}" stepKey="seeCustomerTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" stepKey="seeRetailCustomerTaxClass" /> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" stepKey="seeTaxableGoodsTaxClass" /> + <seeInField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithCustomPriority.priority}}" stepKey="seePriority"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml new file mode 100644 index 0000000000000..72adf7b0dae1e --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteTaxRuleTest"> + <annotations> + <stories value="Delete tax rule"/> + <title value="Delete tax rule"/> + <description value="Test log in to tax rule and Delete tax rule"/> + <testCaseId value="MC-5823"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="defaultTaxRule" stepKey="initialTaxRule"/> + <createData entity="ApiSimplePrice100Qty100v2" stepKey="simpleProduct"/> + <createData entity="Simple_US_Utah_Customer" stepKey="customer" /> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData stepKey="deleteSimpleProduct" createDataKey="simpleProduct" /> + <deleteData stepKey="deleteCustomer" createDataKey="customer" /> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters1"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="$$initialTaxRule.code$$" stepKey="fillTaxCodeSearch"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch1"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow1"/> + + <!-- Delete values on the tax rule form page --> + <click selector="{{AdminTaxRuleFormSection.deleteRule}}" stepKey="clickDeleteRule"/> + <click selector="{{AdminTaxRuleFormSection.ok}}" stepKey="clickOk"/> + <waitForPageLoad stepKey="waitForTaxRuleDeleted" /> + + <!-- Verify we see success message --> + <see selector="{{AdminMessagesSection.success}}" userInput="The tax rule has been deleted." stepKey="seeAssertTaxRuleDeleteMessage"/> + + <!-- Confirm Deleted Tax Rule(from the above step) on the tax rule grid page --> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex2"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="$$initialTaxRule.code$$" stepKey="fillTaxCodeSearch2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <see selector="{{AdminTaxRuleGridSection.emptyText}}" userInput="We couldn't find any records." stepKey="seeAssertTaxRuleNotFound"/> + + <!-- Verify customer don't tax on the store front product page --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$customer$$" /> + </actionGroup> + <amOnPage url="{{StorefrontProductPage.url($$simpleProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPageOnStorefront"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$simpleProduct$$" /> + <argument name="productCount" value="1" /> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openShoppingCart" /> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_Utah" /> + </actionGroup> + <scrollTo selector="{{StorefrontProductPageSection.orderTotal}}" x="0" y="-80" stepKey="scrollToOrderTotal"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.subTotal}}" time="30" stepKey="waitSubtotalAppears"/> + <see selector="{{StorefrontProductPageSection.subTotal}}" userInput="$100.00" stepKey="seeSubTotal"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.shipping}}" time="30" stepKey="waitShippingAppears"/> + <see selector="{{StorefrontProductPageSection.shipping}}" userInput="$5.00" stepKey="seeShipping"/> + <dontSee selector="{{StorefrontProductPageSection.tax}}" stepKey="dontSeeAssertTaxAmount" /> + <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="$105.00" stepKey="seeAssertOrderTotal"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml index 05b85a3a55cb1..4741898b0ab86 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml @@ -123,9 +123,10 @@ <!-- Select shipping --> <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> - + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="SelectCheckMoneyPaymentMethod" after="selectFlatRateShipping" stepKey="selectCheckMoneyPayment"/> <!--Submit Order and verify information--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder" after="selectFlatRateShipping"/> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" after="selectCheckMoneyPayment" stepKey="clickSubmitOrder"/> <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateDefaultTaxRuleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateDefaultTaxRuleTest.xml new file mode 100644 index 0000000000000..0390542544060 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateDefaultTaxRuleTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateDefaultTaxRuleTest"> + <annotations> + <stories value="Update tax rule"/> + <title value="Update tax rule, tax rule default"/> + <description value="Test log in to Update tax rule and Update default tax rule"/> + <testCaseId value="MC-5819"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="defaultTaxRule" stepKey="initialTaxRule"/> + <createData entity="defaultTaxRate" stepKey="initialTaxRate"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData stepKey="deleteTaxRule" createDataKey="initialTaxRule" /> + <deleteData stepKey="deleteTaxRate" createDataKey="initialTaxRate" /> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters1"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="$$initialTaxRule.code$$" stepKey="fillTaxCodeSearch"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch1"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow1"/> + + <!-- Update tax rule with default --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$initialTaxRate.code$$)}}" stepKey="clickSelectNeededItem"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <wait stepKey="waitForAdditionalSettings" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.customerTaxClassOption($$customerTaxClass.class_name$$)}}" stepKey="clickSelectCustomerTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <fillField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithUpdatePriorityPosition.priority}}" stepKey="fillPriority"/> + <fillField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithUpdatePriorityPosition.position}}" stepKey="fillPosition"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see updated tax rule with default(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see updated tax rule with default on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$initialTaxRate.code$$)}}" stepKey="seeTaxRateSelected"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected($$customerTaxClass.class_name$$)}}" stepKey="seeCustomerTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminTaxRuleFormSection.priority}}" userInput="{{taxRuleWithUpdatePriorityPosition.priority}}" stepKey="seePriority"/> + <seeInField selector="{{AdminTaxRuleFormSection.sortOrder}}" userInput="{{taxRuleWithUpdatePriorityPosition.position}}" stepKey="seePosition"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithCustomClassesTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithCustomClassesTest.xml new file mode 100644 index 0000000000000..0e87f268f8825 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithCustomClassesTest.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateTaxRuleWithCustomClassesTest"> + <annotations> + <stories value="Update tax rule"/> + <title value="Update tax rule, tax rule with custom classes"/> + <description value="Test log in to Update tax rule and Update tax rule with custom classes"/> + <testCaseId value="MC-5820"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="defaultTaxRule" stepKey="initialTaxRule"/> + <createData entity="defaultTaxRateWithZipRange" stepKey="taxRateWithZipRange"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData stepKey="deleteTaxRule" createDataKey="initialTaxRule" /> + <deleteData stepKey="deleteTaxRate" createDataKey="taxRateWithZipRange" /> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters1"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="$$initialTaxRule.code$$" stepKey="fillTaxCodeSearch"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch1"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow1"/> + + <!-- Update tax rule with custom classes --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$taxRateWithZipRange.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$taxRateWithZipRange.code$$)}}" stepKey="clickSelectNeededItem"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <wait stepKey="waitForAdditionalSettings" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see updated tax rule with default(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see updated tax rule with default on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$taxRateWithZipRange.code$$)}}" stepKey="seeTaxRateSelected"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + <seeElement selector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" stepKey="seeRetailCustomerTaxClass" /> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" stepKey="seeTaxableGoodsTaxClass" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml new file mode 100644 index 0000000000000..3ef279e4a76a2 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateTaxRuleWithFixedZipUtahTest"> + <annotations> + <stories value="Update tax rule"/> + <title value="Update tax rule, fixed zip Utah"/> + <description value="Test log in to Update tax rule and Update tax rule with fixed zip Utah"/> + <testCaseId value="MC-5821"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="defaultTaxRule" stepKey="initialTaxRule"/> + <createData entity="TaxRateWithFixedZipUtah" stepKey="taxRateWithFixedZipUtah"/> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClass"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <createData entity="ApiSimplePrice100Qty100v2" stepKey="simpleProduct"/> + <createData entity="Simple_US_Utah_Customer" stepKey="customer" /> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData stepKey="deleteTaxRule" createDataKey="initialTaxRule" /> + <deleteData stepKey="deleteTaxRate" createDataKey="taxRateWithFixedZipUtah" /> + <deleteData stepKey="deleteCustomerTaxClass" createDataKey="createCustomerTaxClass"/> + <deleteData stepKey="deleteProductTaxClass" createDataKey="createProductTaxClass"/> + <deleteData stepKey="deleteSimpleProduct" createDataKey="simpleProduct" /> + <deleteData stepKey="deleteCustomer" createDataKey="customer" /> + </after> + + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters1"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="$$initialTaxRule.code$$" stepKey="fillTaxCodeSearch"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch1"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow1"/> + + <!-- Update tax rule with fixed zip Utah --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$taxRateWithFixedZipUtah.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$taxRateWithFixedZipUtah.code$$)}}" stepKey="clickSelectNeededItem"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings"/> + <wait stepKey="waitForAdditionalSettings" time="5" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.customerTaxClassOption(retailCustomerTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.customerTaxClassSelected(retailCustomerTaxClass.class_name)}}" visible="false" stepKey="checkRetailCustomerTaxClass" /> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxableGoodsTaxClass.class_name)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxableGoodsTaxClass.class_name)}}" visible="false" stepKey="checkTaxableGoodsTaxClass" /> + <click selector="{{AdminTaxRuleFormSection.productTaxClassOption($$productTaxClass.class_name$$)}}" stepKey="clickSelectProductTaxClass"/> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="clickSaveTaxRule"/> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="seeAssertTaxRuleSuccessMessage"/> + + <!-- Verify we see updated tax rule with fixed zip Utah(from the above step) on the tax rule grid page --> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRuleGridSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode2"/> + <click selector="{{AdminTaxRuleGridSection.search}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForTaxRuleSearch"/> + <click selector="{{AdminTaxRuleGridSection.nthRow('1')}}" stepKey="clickFirstRow2"/> + + <!-- Verify we see updated tax rule with fixed zip Utah on the tax rule form page --> + <seeInField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="seeInTaxRuleCode"/> + <seeElement selector="{{AdminTaxRuleFormSection.taxRateSelected($$taxRateWithFixedZipUtah.code$$)}}" stepKey="seeTaxRateSelected"/> + <click selector="{{AdminTaxRuleFormSection.additionalSettings}}" stepKey="clickAdditionalSettings1"/> + <scrollTo selector="{{AdminTaxRuleFormSection.additionalSettings}}" x="0" y="-80" stepKey="scrollToAdvancedSettings1"/> + <seeElement selector="{{AdminTaxRuleFormSection.productTaxClassSelected($$productTaxClass.class_name$$)}}" stepKey="seeProductTaxClass"/> + + <!-- Verify if tax rule is applied on the store front product page --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$customer$$" /> + </actionGroup> + <amOnPage url="{{StorefrontProductPage.url($$simpleProduct.custom_attributes[url_key]$$)}}" stepKey="goProductPageOnStorefront"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$simpleProduct$$" /> + <argument name="productCount" value="1" /> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openShoppingCart" /> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_Utah" /> + </actionGroup> + <scrollTo selector="{{StorefrontProductPageSection.orderTotal}}" x="0" y="-80" stepKey="scrollToOrderTotal"/> + <see selector="{{StorefrontProductPageSection.tax}}" userInput="$20.00" stepKey="seeAssertTaxAmount" /> + <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="$125.00" stepKey="seeAssertGrandTotal"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml new file mode 100644 index 0000000000000..5f855f5bb750d --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteTaxRateEntityTest"> + <annotations> + <stories value="Delete Tax Rate"/> + <title value="Delete tax rate"/> + <description value="Test log in to Tax Rate and Delete Tax Rate"/> + <testCaseId value="MC-5801"/> + <severity value="CRITICAL"/> + <group value="tax"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="defaultTaxRate" stepKey="initialTaxRate"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!-- Search the tax rate on tax grid page --> + <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRateIndex1"/> + <waitForPageLoad stepKey="waitForTaxRateIndex1"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters1"/> + <fillField selector="{{AdminTaxRateGridSection.filterByTaxIdentifier}}" userInput="$$initialTaxRate.code$$" stepKey="fillCode"/> + <click selector="{{AdminTaxRateGridSection.search}}" stepKey="clickSearch1"/> + <click selector="{{AdminTaxRateGridSection.nthRow('1')}}" stepKey="clickFirstRow1"/> + + <!-- Delete values on the tax rate form page --> + <click selector="{{AdminTaxRateFormSection.deleteRate}}" stepKey="clickDeleteRate"/> + <click selector="{{AdminTaxRateFormSection.ok}}" stepKey="clickOk"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You Deleted the tax rate." stepKey="seeSuccess1"/> + + <!-- Confirm Deleted TaxIdentifier(from the above step) on the tax rate grid page --> + <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRateIndex2"/> + <waitForPageLoad stepKey="waitForTaxRateIndex2"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickClearFilters2"/> + <fillField selector="{{AdminTaxRateGridSection.filterByTaxIdentifier}}" userInput="{{defaultTaxRate.code}}" stepKey="fillTaxIdentifierField3"/> + <click selector="{{AdminTaxRateGridSection.search}}" stepKey="clickSearch2"/> + <see selector="{{AdminTaxRateGridSection.emptyText}}" userInput="We couldn't find any records." stepKey="seeSuccess"/> + + <!-- Confirm Deleted TaxIdentifier on the tax rule grid page --> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex3"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <dontSee selector="{{AdminTaxRuleFormSection.fieldTaxRate}}" userInput="$$initialTaxRate.code$$" stepKey="dontSeeInTaxRuleForm"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index 1b3422011a9a7..1cb96b37cc760 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -63,6 +63,7 @@ <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> </before> <after> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> @@ -88,9 +89,13 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> - <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="checkCustomerCountry" /> + <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkCustomerRegion" /> + <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> + <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> + <expectedResult type="string">{{US_Address_CA.postcode}}</expectedResult> + <actualResult type="variable">grabTextPostCode</actualResult> + </assertEquals> <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkFPTAmountCA" /> <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index 3fa9826512934..190263efb2469 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -38,6 +38,7 @@ <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> </before> <after> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> @@ -60,9 +61,13 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> - <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_NY.country_id}}" stepKey="checkCustomerCountry" /> + <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_NY.state}}" stepKey="checkCustomerRegion" /> + <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> + <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> + <expectedResult type="string">{{US_Address_NY.postcode}}</expectedResult> + <actualResult type="variable">grabTextPostCode</actualResult> + </assertEquals> <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="expandTaxSummary"/> <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 9ea4793242fe8..bce9d895e311e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -84,6 +84,8 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/> <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkFPTAmountCA" /> <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index 06f6abb2973e7..74233bbff4a64 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -56,6 +56,8 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/> <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmountCA" /> <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml index 7263db10073e0..05ced7e61b3b7 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml @@ -75,7 +75,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -193,7 +193,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -300,7 +300,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -320,6 +320,9 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/> <see stepKey="seeTax3" selector="{{CheckoutPaymentSection.tax}}" userInput="$10.15"/> <click stepKey="expandTax2" selector="{{CheckoutPaymentSection.tax}}"/> <see stepKey="seeTaxPercent2" selector="{{CheckoutPaymentSection.taxPercentage}}" userInput="({{SimpleTaxCA.rate}}%)"/> @@ -407,7 +410,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -428,6 +431,9 @@ <!-- Assert that taxes are applied correctly for CA --> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/> <see stepKey="seeTax3" selector="{{CheckoutPaymentSection.tax}}" userInput="$8.25"/> <click stepKey="expandTax2" selector="{{CheckoutPaymentSection.tax}}"/> <see stepKey="seeTaxPercent2" selector="{{CheckoutPaymentSection.taxPercentage}}" userInput="({{SimpleTaxCA.rate}}%)"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml index 1ce94001e55d1..e7bf08257ea69 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml @@ -66,7 +66,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -92,9 +92,10 @@ <see stepKey="seeTotalExcl2" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$$virtualProduct1.price$$"/> <!-- Change the address --> - <actionGroup ref="GuestCheckoutFillNewBillingAddressActionGroup" stepKey="changeAddress"> + <actionGroup ref="GuestCheckoutSelectPaymentAndFillNewBillingAddressActionGroup" stepKey="changeAddress"> <argument name="customerVar" value="Simple_US_Customer_NY"/> <argument name="customerAddressVar" value="US_Address_NY"/> + <argument name="paymentMethod" value="Check / Money order"/> </actionGroup> <click stepKey="saveAddress" selector="{{CheckoutShippingSection.updateAddress}}"/> @@ -178,7 +179,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -289,7 +290,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -313,6 +314,8 @@ </actionGroup> <click stepKey="clickNext" selector="{{CheckoutShippingSection.next}}"/> <waitForPageLoad stepKey="waitForAddressToLoad"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <see stepKey="seeAddress" selector="{{CheckoutShippingSection.defaultShipping}}" userInput="{{SimpleTaxCA.state}}"/> <see stepKey="seeShipTo" selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{SimpleTaxCA.state}}"/> @@ -331,6 +334,8 @@ </actionGroup> <click stepKey="clickNext2" selector="{{CheckoutShippingSection.next}}"/> <waitForPageLoad stepKey="waitForShippingToLoad"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> <see stepKey="seeShipTo2" selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{SimpleTaxNY.state}}"/> <!-- Assert that taxes are applied correctly for NY --> @@ -409,7 +414,7 @@ <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> </actionGroup> - <!-- Ensure tax wont be shown in the cart --> + <!-- Ensure tax won't be shown in the cart --> <actionGroup ref="changeToDefaultTaxConfigurationUI" stepKey="changeToDefaultTaxConfiguration"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> @@ -426,6 +431,8 @@ <!-- Assert that taxes are applied correctly for NY --> <amOnPage url="{{CheckoutPage.url}}" stepKey="goToCheckout"/> <waitForPageLoad stepKey="waitForShippingSection"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <see stepKey="seeAddress" selector="{{CheckoutShippingSection.defaultShipping}}" userInput="{{SimpleTaxNY.state}}"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> @@ -447,6 +454,8 @@ <click stepKey="saveAddress" selector="{{CheckoutShippingSection.updateAddress}}"/> <waitForPageLoad stepKey="waitForAddressSaved"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> <see stepKey="seeAddress2" selector="{{CheckoutShippingSection.defaultShipping}}" userInput="{{SimpleTaxCA.state}}"/> <!-- Assert that taxes are applied correctly for CA --> diff --git a/app/code/Magento/Tax/view/base/web/js/price/adjustment.js b/app/code/Magento/Tax/view/base/web/js/price/adjustment.js index 9af15f84562f4..a17d130d9282a 100644 --- a/app/code/Magento/Tax/view/base/web/js/price/adjustment.js +++ b/app/code/Magento/Tax/view/base/web/js/price/adjustment.js @@ -62,7 +62,7 @@ define([ }, /** - * Set price taax type. + * Set price tax type. * * @param {String} priceType * @return {Object} diff --git a/app/code/Magento/Theme/Block/Html/Header/Logo.php b/app/code/Magento/Theme/Block/Html/Header/Logo.php index 0a0e71f44ba32..b51f624c20339 100644 --- a/app/code/Magento/Theme/Block/Html/Header/Logo.php +++ b/app/code/Magento/Theme/Block/Html/Header/Logo.php @@ -43,6 +43,10 @@ public function __construct( /** * Check if current url is url for home page * + * @deprecated This function is no longer used. It was previously used by + * Magento/Theme/view/frontend/templates/html/header/logo.phtml + * to check if the logo should be clickable on the homepage. + * * @return bool */ public function isHomePage() diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml new file mode 100644 index 0000000000000..a4088c7a4a0b7 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontHeaderSection"> + <element name="welcomeMessage" type="text" selector=".greet.welcome"/> + </section> +</sections> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml index 4395cab651de1..1103ae28741c6 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml @@ -15,11 +15,11 @@ $welcomeMessage = $block->getWelcome(); case 'welcome': ?> <li class="greet welcome" data-bind="scope: 'customer'"> <!-- ko if: customer().fullname --> - <span data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().fullname)"> + <span class="logged-in" data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().fullname)"> </span> <!-- /ko --> <!-- ko ifnot: customer().fullname --> - <span data-bind='html:"<?= $block->escapeHtml($welcomeMessage) ?>"'></span> + <span class="not-logged-in" data-bind='html:"<?= $block->escapeHtml($welcomeMessage) ?>"'></span> <?= $block->getBlockHtml('header.additional') ?> <!-- /ko --> </li> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml index 17f8d7c70f574..79b891b7e55e6 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml @@ -12,19 +12,11 @@ ?> <?php $storeName = $block->getThemeName() ? $block->getThemeName() : $block->getLogoAlt();?> <span data-action="toggle-nav" class="action nav-toggle"><span><?= /* @escapeNotVerified */ __('Toggle Nav') ?></span></span> -<?php if ($block->isHomePage()):?> - <strong class="logo"> -<?php else: ?> - <a class="logo" href="<?= $block->getUrl('') ?>" title="<?= /* @escapeNotVerified */ $storeName ?>"> -<?php endif ?> - <img src="<?= /* @escapeNotVerified */ $block->getLogoSrc() ?>" - title="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" - alt="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" - <?= $block->getLogoWidth() ? 'width="' . $block->getLogoWidth() . '"' : '' ?> - <?= $block->getLogoHeight() ? 'height="' . $block->getLogoHeight() . '"' : '' ?> - /> -<?php if ($block->isHomePage()):?> - </strong> -<?php else:?> - </a> -<?php endif?> +<a class="logo" href="<?= $block->getUrl('') ?>" title="<?= /* @escapeNotVerified */ $storeName ?>"> + <img src="<?= /* @escapeNotVerified */ $block->getLogoSrc() ?>" + title="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" + alt="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" + <?= $block->getLogoWidth() ? 'width="' . $block->getLogoWidth() . '"' : '' ?> + <?= $block->getLogoHeight() ? 'height="' . $block->getLogoHeight() . '"' : '' ?> + /> +</a> diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js index 5f05d3f3015ad..f74282afd32a6 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js @@ -1856,7 +1856,7 @@ } }; - // Applies formatting to the caret postion + // Applies formatting to the caret position function applyCaretFormat() { var rng, caretContainer, textNode, offset, bookmark, container, text; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Writer.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Writer.js index 2abfac6478a0a..f37607f73270b 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Writer.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Writer.js @@ -110,7 +110,7 @@ tinymce.html.Writer = function(settings) { * * @method text * @param {String} text String to write out. - * @param {Boolean} raw Optional raw state if true the contents wont get encoded. + * @param {Boolean} raw Optional raw state if true the contents won't get encoded. */ text: function(text, raw) { if (text.length > 0) diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/example/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/example/editor_plugin_src.js index edc1e776e5524..bd805e6370274 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/example/editor_plugin_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/example/editor_plugin_src.js @@ -49,7 +49,7 @@ }, /** - * Creates control instances based in the incomming name. This method is normally not + * Creates control instances based in the incoming name. This method is normally not * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons * but you sometimes need to create more complex controls like listboxes, split buttons etc then this * method can be used to create those. diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js index b3d77f6ce3139..2d9d859caa6fa 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js @@ -15682,7 +15682,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; - // Applies formatting to the caret postion + // Applies formatting to the caret position function applyCaretFormat() { var rng, caretContainer, textNode, offset, bookmark, container, text; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js index daf2ad4e71bac..aaa207da3e4a9 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js @@ -16532,7 +16532,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; - // Applies formatting to the caret postion + // Applies formatting to the caret position function applyCaretFormat() { var rng, caretContainer, textNode, offset, bookmark, container, text; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js index 2634633d8eee5..8448152ed5d2f 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js @@ -16506,7 +16506,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; - // Applies formatting to the caret postion + // Applies formatting to the caret position function applyCaretFormat() { var rng, caretContainer, textNode, offset, bookmark, container, text; diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media.php index c2460fd8385c1..d4b1937bd06ba 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media.php @@ -31,14 +31,16 @@ public function prepare() { if ($this->getData('config/uploaderConfig/url')) { $url = $this->getContext()->getUrl($this->getData('config/uploaderConfig/url'), ['_secure' => true]); + $updateConfig = [ + 'uploaderConfig' => ['url' => $url] + ]; + if (!isset($this->getConfiguration()['dataScope'])) { + $updateConfig['dataScope'] = $this->getName(); + } $data = array_replace_recursive( $this->getData(), [ - 'config' => [ - 'uploaderConfig' => [ - 'url' => $url - ], - ], + 'config' => $updateConfig, ] ); $this->setData($data); diff --git a/app/code/Magento/Ui/Component/Form/Fieldset.php b/app/code/Magento/Ui/Component/Form/Fieldset.php index 745068ca0fa8a..2525d0a4923c1 100644 --- a/app/code/Magento/Ui/Component/Form/Fieldset.php +++ b/app/code/Magento/Ui/Component/Form/Fieldset.php @@ -5,13 +5,11 @@ */ namespace Magento\Ui\Component\Form; -use Magento\Ui\Component\Container; use Magento\Ui\Component\AbstractComponent; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Framework\View\Element\UiComponentInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; /** + * Fieldset UI Component. + * * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Ui/Component/Layout/Tabs.php b/app/code/Magento/Ui/Component/Layout/Tabs.php index 8ceac716ae218..0d176d7241653 100644 --- a/app/code/Magento/Ui/Component/Layout/Tabs.php +++ b/app/code/Magento/Ui/Component/Layout/Tabs.php @@ -5,18 +5,17 @@ */ namespace Magento\Ui\Component\Layout; -use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\UiComponent\BlockWrapperInterface; use Magento\Framework\View\Element\UiComponent\DataSourceInterface; -use Magento\Framework\View\Element\UiComponent\LayoutInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponentInterface; +use Magento\Framework\View\Element\ComponentVisibilityInterface; use Magento\Ui\Component\Layout\Tabs\TabInterface; /** * Class Tabs */ -class Tabs extends \Magento\Framework\View\Layout\Generic implements LayoutInterface +class Tabs extends \Magento\Framework\View\Layout\Generic { /** * @var string @@ -89,58 +88,15 @@ protected function addChildren(array &$topNode, UiComponentInterface $component, $this->addWrappedBlock($childComponent, $childrenAreas); continue; } + if ($childComponent instanceof ComponentVisibilityInterface && !$childComponent->isComponentVisible()) { + continue; + } $name = $childComponent->getName(); $config = $childComponent->getData('config'); $collectedComponents[$name] = true; - if (isset($config['is_collection']) && $config['is_collection'] === true) { - $label = $childComponent->getData('config/label'); - $this->component->getContext()->addComponentDefinition( - 'collection', - [ - 'component' => 'Magento_Ui/js/form/components/collection', - 'extends' => $this->namespace - ] - ); - - /** - * @var UiComponentInterface $childComponent - * @var array $structure - */ - list($childComponent, $structure) = $this->prepareChildComponents($childComponent, $name); - - $childrenStructure = $structure[$name]['children']; - - $structure[$name]['children'] = [ - $name . '_collection' => [ - 'type' => 'collection', - 'config' => [ - 'active' => 1, - 'removeLabel' => __('Remove %1', $label), - 'addLabel' => __('Add New %1', $label), - 'removeMessage' => $childComponent->getData('config/removeMessage'), - 'itemTemplate' => 'item_template', - ], - 'children' => [ - 'item_template' => ['type' => $this->namespace, - 'isTemplate' => true, - 'component' => 'Magento_Ui/js/form/components/collection/item', - 'childType' => 'group', - 'config' => [ - 'label' => __('New %1', $label), - ], - 'children' => $childrenStructure - ] - ] - ] - ]; - } else { - /** - * @var UiComponentInterface $childComponent - * @var array $structure - */ - list($childComponent, $structure) = $this->prepareChildComponents($childComponent, $name); - } + + [$childComponent, $structure] = $this->buildChildComponentStructure($config, $childComponent); $tabComponent = $this->createTabComponent($childComponent, $name); @@ -168,6 +124,67 @@ protected function addChildren(array &$topNode, UiComponentInterface $component, $topNode = $this->structure; } + /** + * Build child components structure of the tab + * + * @param array $config + * @param UiComponentInterface $childComponent + * @return array + */ + private function buildChildComponentStructure(array $config, $childComponent): array + { + $name = $childComponent->getName(); + if (isset($config['is_collection']) && $config['is_collection'] === true) { + $label = $childComponent->getData('config/label'); + $this->component->getContext()->addComponentDefinition( + 'collection', + [ + 'component' => 'Magento_Ui/js/form/components/collection', + 'extends' => $this->namespace + ] + ); + /** + * @var UiComponentInterface $childComponent + * @var array $structure + */ + [$childComponent, $structure] = $this->prepareChildComponents($childComponent, $name); + + $childrenStructure = $structure[$name]['children']; + + $structure[$name]['children'] = [ + $name . '_collection' => [ + 'type' => 'collection', + 'config' => [ + 'active' => 1, + 'removeLabel' => __('Remove %1', $label), + 'addLabel' => __('Add New %1', $label), + 'removeMessage' => $childComponent->getData('config/removeMessage'), + 'itemTemplate' => 'item_template', + ], + 'children' => [ + 'item_template' => ['type' => $this->namespace, + 'isTemplate' => true, + 'component' => 'Magento_Ui/js/form/components/collection/item', + 'childType' => 'group', + 'config' => [ + 'label' => __('New %1', $label), + ], + 'children' => $childrenStructure + ] + ] + ] + ]; + } else { + /** + * @var UiComponentInterface $childComponent + * @var array $structure + */ + [$childComponent, $structure] = $this->prepareChildComponents($childComponent, $name); + } + + return [$childComponent, $structure]; + } + /** * Add wrapped layout block * diff --git a/app/code/Magento/Ui/Component/Listing/Columns/Date.php b/app/code/Magento/Ui/Component/Listing/Columns/Date.php index 1c9916a941458..ad876b66b6038 100644 --- a/app/code/Magento/Ui/Component/Listing/Columns/Date.php +++ b/app/code/Magento/Ui/Component/Listing/Columns/Date.php @@ -11,6 +11,8 @@ use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** + * Date format column + * * @api * @since 100.0.2 */ @@ -47,6 +49,27 @@ public function __construct( parent::__construct($context, $uiComponentFactory, $components, $data); } + /** + * @inheritdoc + */ + public function prepare() + { + $config = $this->getData('config'); + $config['filter'] = [ + 'filterType' => 'dateRange', + 'templates' => [ + 'date' => [ + 'options' => [ + 'dateFormat' => $this->timezone->getDateFormatWithLongYear() + ] + ] + ] + ]; + $this->setData('config', $config); + + parent::prepare(); + } + /** * @inheritdoc */ diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index e249a64861d43..9304d25cc8a98 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -5,6 +5,8 @@ */ namespace Magento\Ui\TemplateEngine\Xhtml; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\JsonHexTag; use Magento\Framework\View\Layout\Generator\Structure; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\TemplateEngine\Xhtml\Template; @@ -42,25 +44,33 @@ class Result implements ResultInterface */ protected $logger; + /** + * @var JsonHexTag + */ + private $jsonSerializer; + /** * @param Template $template * @param CompilerInterface $compiler * @param UiComponentInterface $component * @param Structure $structure * @param LoggerInterface $logger + * @param JsonHexTag $jsonSerializer */ public function __construct( Template $template, CompilerInterface $compiler, UiComponentInterface $component, Structure $structure, - LoggerInterface $logger + LoggerInterface $logger, + JsonHexTag $jsonSerializer = null ) { $this->template = $template; $this->compiler = $compiler; $this->component = $component; $this->structure = $structure; $this->logger = $logger; + $this->jsonSerializer = $jsonSerializer ?? ObjectManager::getInstance()->get(JsonHexTag::class); } /** @@ -81,7 +91,7 @@ public function getDocumentElement() public function appendLayoutConfiguration() { $layoutConfiguration = $this->wrapContent( - json_encode($this->structure->generate($this->component), JSON_HEX_TAG) + $this->jsonSerializer->serialize($this->structure->generate($this->component)) ); $this->template->append($layoutConfiguration); } diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml index 73d441dd96d1e..85f02e42345a7 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml @@ -14,7 +14,7 @@ <argument name="value" type="string"/> </arguments> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector="(//*[contains(@class, 'admin__data-grid-header')][contains(@data-bind, 'afterRender: \$data.setToolbarNode')]//*[contains(@class, 'admin__data-grid-filters-current')][contains(@class, '_show')])[1]" visible="true" stepKey="clearTheFiltersIfPresent"/> + <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector="{{AdminGridFilterControls.clearAll}}" visible="true" stepKey="clearTheFiltersIfPresent"/> <waitForPageLoad stepKey="waitForPageLoad" time="5"/> <click selector="{{AdminGridFilterControls.filters}}" stepKey="clickOnFilters1"/> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml index edcc70a82396d..a2b7ba8c1ffd5 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml @@ -18,5 +18,6 @@ <!--Specific cell e.g. {{Section.gridCell('1', 'Name')}}--> <element name="gridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> <element name="rowViewAction" type="button" selector=".data-grid tbody > tr:nth-of-type({{row}}) .action-menu-item" parameterized="true" timeout="30"/> + <element name="dataGridEmpty" type="block" selector=".data-grid-tr-no-data td"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml index cb1fdd716b174..75d445f1ee04e 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml @@ -11,5 +11,6 @@ <section name="AdminMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> <element name="errorMessage" type="text" selector=".message.message-error.error"/> + <element name="warningMessage" type="text" selector=".message-warning"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/MediaTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/MediaTest.php index 7af173cf2a96f..9653ecd7ded44 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/MediaTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/MediaTest.php @@ -21,6 +21,9 @@ class MediaTest extends \PHPUnit\Framework\TestCase /** @var Media */ protected $media; + /** + * @inheritdoc + */ public function setUp() { $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) @@ -29,8 +32,13 @@ public function setUp() ->getMockForAbstractClass(); $this->media = new Media($this->context); + } + + public function testPrepareWithoutDataScope() + { $this->media->setData( [ + 'name' => 'test_name', 'config' => [ 'uploaderConfig' => [ 'url' => 'module/actionPath/path' @@ -38,19 +46,54 @@ public function setUp() ], ] ); + $url = 'http://magento2.com/module/actionPath/path/key/34523456234523trdg'; + $expectedConfig = [ + 'uploaderConfig' => ['url' => $url], + 'dataScope' => 'test_name' + ]; + + $this->processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->context->expects($this->atLeastOnce())->method('getProcessor')->willReturn($this->processor); + $this->context->expects($this->once()) + ->method('getUrl') + ->with('module/actionPath/path', ['_secure' => true]) + ->willReturn($url); + $this->media->prepare(); + $configuration = $this->media->getConfiguration(); + $this->assertEquals($expectedConfig, $configuration); } - public function testPrepare() + public function testPrepareWithDataScope() { + $this->media->setData( + [ + 'name' => 'test_name', + 'config' => [ + 'dataScope' => 'other_data_scope', + 'uploaderConfig' => [ + 'url' => 'module/actionPath/path' + ], + ], + ] + ); + $url = 'http://magento2.com/module/actionPath/path/key/34523456234523trdg'; + $expectedConfig = [ + 'uploaderConfig' => ['url' => $url], + 'dataScope' => 'other_data_scope' + ]; + $this->processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) ->disableOriginalConstructor() ->getMock(); $this->context->expects($this->atLeastOnce())->method('getProcessor')->willReturn($this->processor); - $url = 'http://magento2.com/module/actionPath/path/key/34523456234523trdg'; $this->context->expects($this->once()) ->method('getUrl') ->with('module/actionPath/path', ['_secure' => true]) ->willReturn($url); $this->media->prepare(); + $configuration = $this->media->getConfiguration(); + $this->assertEquals($expectedConfig, $configuration); } } diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/FieldsetTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/FieldsetTest.php new file mode 100644 index 0000000000000..d243507580807 --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/FieldsetTest.php @@ -0,0 +1,58 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Ui\Test\Unit\Component\Form; + +use Magento\Ui\Component\Form\Fieldset; +use Magento\Framework\View\Element\UiComponent\ContextInterface; + +/** + * Class FieldTest + * + * Test for class \Magento\Ui\Component\Form\Fieldset + */ +class FieldsetTest extends \PHPUnit\Framework\TestCase +{ + const NAME = 'fieldset'; + + /** + * @var Fieldset + */ + protected $fieldset; + + /** + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) + ->getMockForAbstractClass(); + + $this->fieldset = new Fieldset( + $this->context, + [], + [] + ); + } + + /** + * Run test for getComponentName() method + * + * @return void + * + */ + public function testGetComponentName() + { + $this->assertEquals(self::NAME, $this->fieldset->getComponentName()); + } +} diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/area.js b/app/code/Magento/Ui/view/base/web/js/form/components/area.js index 5967b8c57b5d3..741e6d3065023 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/area.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/area.js @@ -44,7 +44,7 @@ define([ /** * Calls parent's initElement method. - * Assignes callbacks on various events of incoming element. + * Assigns callbacks on various events of incoming element. * @param {Object} elem * @return {Object} - reference to instance */ diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/collection.js b/app/code/Magento/Ui/view/base/web/js/form/components/collection.js index 2c12486ceb519..c31963e98627d 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/collection.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/collection.js @@ -91,7 +91,7 @@ define([ }, /** - * Returnes true if current set of items differ from initial one, + * Returns true if current set of items differ from initial one, * or if some child has been changed. * * @returns {Boolean} @@ -153,6 +153,7 @@ define([ * Creates function that removes element * from collection using '_removeChild' method. * @param {Object} elem - Element that should be removed. + * @deprecated Not used anymore */ removeAddress: function (elem) { var self = this; @@ -169,7 +170,7 @@ define([ }, /** - * Removes elememt from both collection and data storage, + * Removes element from both collection and data storage, * activates first element if removed one was active, * triggers 'update' event. * diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/collection/item.js b/app/code/Magento/Ui/view/base/web/js/form/components/collection/item.js index c2a65371471c5..045c25ab7911f 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/collection/item.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/collection/item.js @@ -19,7 +19,7 @@ define([ }; /** - * Parses incoming data and returnes result merged with default preview config + * Parses incoming data and returns result merged with default preview config * * @param {Object|String} data * @return {Object} diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js index 6d33386fa1f1c..1ddc4dc247b24 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js @@ -68,7 +68,7 @@ define([ /** * Calls parent's initElement method. - * Assignes callbacks on various events of incoming element. + * Assigns callbacks on various events of incoming element. * * @param {Object} elem * @return {Object} - reference to instance diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/checkbox-set.js b/app/code/Magento/Ui/view/base/web/js/form/element/checkbox-set.js index d0c8c13edff5b..254585a62491c 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/checkbox-set.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/checkbox-set.js @@ -124,7 +124,7 @@ define([ }, /** - * Returns option object assoctiated with provided value. + * Returns option object associated with provided value. * * @param {String} value * @returns {Object} diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index 6492d0821fc21..357571350a268 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -324,7 +324,7 @@ define([ /** * Handler which is invoked when files are choosed for upload. - * May be used for implementation of aditional validation rules, + * May be used for implementation of additional validation rules, * e.g. total files and a total size rules. * * @param {Event} e - Event object. diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js index 070761fff53e3..3d02afcc40a9e 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js @@ -20,7 +20,7 @@ define([ return Abstract.extend({ defaults: { elementSelector: 'textarea', - suffixRegExpPattern: '\\${ \\$.wysiwygUniqueSuffix }', + suffixRegExpPattern: '${ $.wysiwygUniqueSuffix }', $wysiwygEditorButton: '', links: { value: '${ $.provider }:${ $.dataScope }' @@ -65,6 +65,7 @@ define([ initConfig: function (config) { var pattern = config.suffixRegExpPattern || this.constructor.defaults.suffixRegExpPattern; + pattern = pattern.replace(/\$/g, '\\$&'); config.content = config.content.replace(new RegExp(pattern, 'g'), this.getUniqueSuffix(config)); this._super(); diff --git a/app/code/Magento/Ui/view/base/web/js/form/provider.js b/app/code/Magento/Ui/view/base/web/js/form/provider.js index d070e23c708bb..49765f589bd9d 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/provider.js +++ b/app/code/Magento/Ui/view/base/web/js/form/provider.js @@ -82,7 +82,7 @@ define([ }, /** - * Set data to provder based on current data. + * Set data to provider based on current data. * * @param {Object} oldData * @param {Object} newData diff --git a/app/code/Magento/Ui/view/base/web/js/grid/dnd.js b/app/code/Magento/Ui/view/base/web/js/grid/dnd.js index 5a71fca673ec9..660e3cb3bd2bb 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/dnd.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/dnd.js @@ -406,7 +406,7 @@ define([ * * @param {Number} x - X coordinate of a grabbed point. * @param {Number} y - Y coordinate of a grabbed point. - * @param {HTMLElement} elem - Grabbed elemenet. + * @param {HTMLElement} elem - Grabbed element. */ grab: function (x, y, elem) { this.initDrag = true; diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js index 589aed4c9837b..a4785aea03743 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js @@ -164,7 +164,7 @@ define([ }, /** - * Adds listeners on a new recrod. + * Adds listeners on a new record. * * @param {Record} record * @returns {Editor} Chainable. @@ -208,7 +208,7 @@ define([ }, /** - * Starts editing of a specfied record. If records' + * Starts editing of a specified record. If records' * instance doesn't exist, than it will be created. * * @param {(Number|String)} id - See 'getId' method. @@ -401,7 +401,7 @@ define([ }, /** - * Disables editing of specfied fields. + * Disables editing of specified fields. * * @param {Array} fields - An array of fields indeces to be disabled. * @returns {Editor} Chainable. diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js index 390aedf193b91..c648875e62d7c 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js @@ -141,7 +141,7 @@ define([ }, /** - * Creates fields for the specfied columns. + * Creates fields for the specified columns. * * @param {Array} columns - An array of column instances. * @returns {Record} Chainable. @@ -280,7 +280,7 @@ define([ }, /** - * Counts total errors ammount accros all fields. + * Counts total errors amount across all fields. * * @returns {Number} */ diff --git a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js index f26ca8697c2ff..3626f52806881 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js @@ -95,7 +95,7 @@ define([ }, /** - * Adds new action. If action with a specfied identifier + * Adds new action. If action with a specified identifier * already exists, than the original one will be overrided. * * @param {Object} action - Action object. diff --git a/app/code/Magento/Ui/view/base/web/js/grid/resize.js b/app/code/Magento/Ui/view/base/web/js/grid/resize.js index 50781c9afd8c3..792c997f060c6 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/resize.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/resize.js @@ -624,7 +624,7 @@ define([ }, /** - * Generate index that will indentify context + * Generate index that will identify context * * @param {Object} ctx * @return {String} diff --git a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js index fa445a2577adb..19536e7ff8c18 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js @@ -102,7 +102,7 @@ define([ /** * Applies search query. * - * @param {String} [value=inputValue] - If not specfied, then + * @param {String} [value=inputValue] - If not specified, then * value of the input field will be used. * @returns {Search} Chainable. */ diff --git a/app/code/Magento/Ui/view/base/web/js/grid/toolbar.js b/app/code/Magento/Ui/view/base/web/js/grid/toolbar.js index 8a17e3ed46f29..fd2d09c6a65f6 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/toolbar.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/toolbar.js @@ -557,7 +557,7 @@ define([ }, /** - * Handles changes of windows' top scroll postion. + * Handles changes of windows' top scroll position. */ onWindowScrollTop: function () { this.updateTableOffset() diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js index 607d217837fcc..eb4f2b39128e2 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js @@ -15,7 +15,7 @@ define([ 'use strict'; /** - * Removes non plain object items from the specfied array. + * Removes non plain object items from the specified array. * * @param {Array} container - Array whose value should be filtered. * @returns {Array} diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js index 34ceced2c6873..87b5b2f5fe8fe 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js @@ -51,7 +51,7 @@ define([ }, /** - * Retrieves specfied item. + * Retrieves specified item. * * @param {String} key - Key of the property to be retrieved. */ @@ -60,7 +60,7 @@ define([ }, /** - * Removes specfied item. + * Removes specified item. * * @param {String} key - Key of the property to be removed. */ diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/bootstrap.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/bootstrap.js index 4518db598b4d3..83d43d2691b3f 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/bootstrap.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/bootstrap.js @@ -26,7 +26,7 @@ define(function (require) { mageInit: require('./mage-init'), keyboard: require('./keyboard'), optgroup: require('./optgroup'), - aferRender: require('./after-render'), + afterRender: require('./after-render'), autoselect: require('./autoselect'), datepicker: require('./datepicker'), outerClick: require('./outer_click'), diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js index cc9dc5e0a2202..98e86de23221d 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js @@ -42,7 +42,7 @@ define([ /** * Document click handler which in case if event target is not * a descendant of provided container element, - * invokes specfied in configuration callback. + * invokes specified in configuration callback. * * @param {HTMLElement} container * @param {Object} config diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/extender/bound-nodes.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/extender/bound-nodes.js index a332b595bdf3c..6b3c437b90508 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/extender/bound-nodes.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/extender/bound-nodes.js @@ -109,7 +109,7 @@ define([ wrapper.extend(ko, { /** - * Extends kncokouts' 'applyBindings' + * Extends knockouts' 'applyBindings' * to track nodes associated with model. * * @param {Function} orig - Original 'applyBindings' method. @@ -136,7 +136,7 @@ define([ }, /** - * Extends kncokouts' cleanNode + * Extends knockouts' cleanNode * to track nodes associated with model. * * @param {Function} orig - Original 'cleanNode' method. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/observable_source.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/observable_source.js index 98ffd83b56466..916666d70d1bd 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/observable_source.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/observable_source.js @@ -29,7 +29,7 @@ define([ * Else, writes into it. * @param {String} key - key to write to or to read from * @param {*} value - * @return {*} - if 1 arg provided, returnes _data[key] property + * @return {*} - if 1 arg provided, Returns _data[key] property */ data: function (key, value) { if (arguments.length === 1) { diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index 31362644d415a..be8fd2ce9fcef 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -60,7 +60,7 @@ define([ } /** - * Validates provied value by a specfied set of rules. + * Validates provied value by a specified set of rules. * * @param {(String|Object)} rules - One or many validation rules. * @param {*} value - Value to be checked. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js index f6afdf7846388..77de8a1ceb0ed 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js @@ -88,7 +88,7 @@ define([ }; /** - * Adds specfied bindings to each DOM elemenet in + * Adds specified bindings to each DOM element in * collection and evalutes them with provided context. * * @param {(Object|Function)} data - Either bindings object or a function diff --git a/app/code/Magento/Ui/view/base/web/templates/form/insert.html b/app/code/Magento/Ui/view/base/web/templates/form/insert.html index e19b2784e6bc6..e590b5e2adedf 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/insert.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/insert.html @@ -6,9 +6,6 @@ --> <div data-bind="bindHtml: content, - attr: { - class: element.cssclass ? element.cssclass : 'admin__scope-old' - }, visible: visible, css: contentSelector"></div> diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index f4aa61b145f62..d8ceb16d71fdc 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -87,7 +87,7 @@ protected function prepareSelect(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ protected function doFindAllByData(array $data) { @@ -95,7 +95,7 @@ protected function doFindAllByData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ protected function doFindOneByData(array $data) { @@ -161,26 +161,22 @@ private function deleteOldUrls(array $urls): void $oldUrlsSelect->from( $this->resource->getTableName(self::TABLE_NAME) ); - /** @var UrlRewrite $url */ - foreach ($urls as $url) { - $oldUrlsSelect->orWhere( - $this->connection->quoteIdentifier( - UrlRewrite::ENTITY_TYPE - ) . ' = ?', - $url->getEntityType() - ); - $oldUrlsSelect->where( - $this->connection->quoteIdentifier( - UrlRewrite::ENTITY_ID - ) . ' = ?', - $url->getEntityId() - ); - $oldUrlsSelect->where( - $this->connection->quoteIdentifier( - UrlRewrite::STORE_ID - ) . ' = ?', - $url->getStoreId() - ); + + $uniqueEntities = $this->prepareUniqueEntities($urls); + foreach ($uniqueEntities as $storeId => $entityTypes) { + foreach ($entityTypes as $entityType => $entities) { + $oldUrlsSelect->orWhere( + $this->connection->quoteIdentifier( + UrlRewrite::STORE_ID + ) . ' = ' . $this->connection->quote($storeId, 'INTEGER') . + ' AND ' . $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_ID + ) . ' IN (' . $this->connection->quote($entities, 'INTEGER') . ')' . + ' AND ' . $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_TYPE + ) . ' = ' . $this->connection->quote($entityType) + ); + } } // prevent query locking in a case when nothing to delete @@ -198,6 +194,28 @@ private function deleteOldUrls(array $urls): void } } + /** + * Prepare array with unique entities + * + * @param UrlRewrite[] $urls + * @return array + */ + private function prepareUniqueEntities(array $urls): array + { + $uniqueEntities = []; + /** @var UrlRewrite $url */ + foreach ($urls as $url) { + $entityIds = (!empty($uniqueEntities[$url->getStoreId()][$url->getEntityType()])) ? + $uniqueEntities[$url->getStoreId()][$url->getEntityType()] : []; + + if (!\in_array($url->getEntityId(), $entityIds)) { + $entityIds[] = $url->getEntityId(); + } + $uniqueEntities[$url->getStoreId()][$url->getEntityType()] = $entityIds; + } + return $uniqueEntities; + } + /** * @inheritDoc */ @@ -289,7 +307,7 @@ protected function createFilterDataBasedOnUrls($urls) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteByData(array $data) { diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php index b96a8ef637404..697ce33be0fa7 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php @@ -445,7 +445,6 @@ public function testReplace() $urlSecond = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); // delete - $urlFirst->expects($this->any()) ->method('getEntityType') ->willReturn('product'); @@ -479,10 +478,6 @@ public function testReplace() ->with(DbStorage::TABLE_NAME) ->will($this->returnValue('table_name')); - $this->connectionMock->expects($this->any()) - ->method('query') - ->with('sql delete query'); - // insert $urlFirst->expects($this->any()) @@ -497,10 +492,6 @@ public function testReplace() ->with(DbStorage::TABLE_NAME) ->will($this->returnValue('table_name')); - $this->connectionMock->expects($this->once()) - ->method('insertMultiple') - ->with('table_name', [['row1'], ['row2']]); - $this->storage->replace([$urlFirst, $urlSecond]); } diff --git a/app/code/Magento/Usps/Model/Carrier.php b/app/code/Magento/Usps/Model/Carrier.php index 1e8faf3cc9614..6e69b9c317946 100644 --- a/app/code/Magento/Usps/Model/Carrier.php +++ b/app/code/Magento/Usps/Model/Carrier.php @@ -366,6 +366,7 @@ public function getResult() /** * @inheritdoc + * * Starting from 23.02.2018 USPS doesn't allow to create free shipping labels via their API. */ public function isShippingLabelsAvailable() @@ -448,7 +449,7 @@ protected function _getXmlQuotes() $package->addChild('FirstClassMailType', 'PARCEL'); } $package->addChild('ZipOrigination', $r->getOrigPostal()); - //only 5 chars avaialble + //only 5 chars available $package->addChild('ZipDestination', substr($r->getDestPostal(), 0, 5)); $package->addChild('Pounds', $r->getWeightPounds()); $package->addChild('Ounces', $r->getWeightOunces()); @@ -1167,6 +1168,7 @@ public function getAllowedMethods() /** * Return USPS county name by country ISO 3166-1-alpha-2 code + * * Return false for unknown countries * * @param string $countryId @@ -1246,7 +1248,7 @@ protected function _getCountryName($countryId) 'FO' => 'Faroe Islands', 'FR' => 'France', 'GA' => 'Gabon', - 'GB' => 'Great Britain and Northern Ireland', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', 'GD' => 'Grenada', 'GE' => 'Georgia, Republic of', 'GF' => 'French Guiana', @@ -1364,7 +1366,7 @@ protected function _getCountryName($countryId) 'ST' => 'Sao Tome and Principe', 'SV' => 'El Salvador', 'SY' => 'Syrian Arab Republic', - 'SZ' => 'Swaziland', + 'SZ' => 'Eswatini', 'TC' => 'Turks and Caicos Islands', 'TD' => 'Chad', 'TG' => 'Togo', diff --git a/app/code/Magento/Vault/Model/PaymentTokenRepository.php b/app/code/Magento/Vault/Model/PaymentTokenRepository.php index 8539d35158214..2ccd6181b9b81 100644 --- a/app/code/Magento/Vault/Model/PaymentTokenRepository.php +++ b/app/code/Magento/Vault/Model/PaymentTokenRepository.php @@ -58,13 +58,15 @@ class PaymentTokenRepository implements PaymentTokenRepositoryInterface private $collectionProcessor; /** - * @param \Magento\Vault\Model\ResourceModel\PaymentToken $resourceModel + * PaymentTokenRepository constructor. + * + * @param PaymentTokenResourceModel $resourceModel * @param PaymentTokenFactory $paymentTokenFactory * @param FilterBuilder $filterBuilder * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param PaymentTokenSearchResultsInterfaceFactory $searchResultsFactory * @param CollectionFactory $collectionFactory - * @param CollectionProcessorInterface | null $collectionProcessor + * @param CollectionProcessorInterface|null $collectionProcessor */ public function __construct( PaymentTokenResourceModel $resourceModel, @@ -99,7 +101,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr $searchResults = $this->searchResultsFactory->create(); $searchResults->setSearchCriteria($searchCriteria); $searchResults->setItems($collection->getItems()); - + $searchResults->setTotalCount($collection->getSize()); return $searchResults; } @@ -140,7 +142,7 @@ public function delete(Data\PaymentTokenInterface $paymentToken) /** * Performs persist operations for a specified payment token. * - * @param \Magento\Vault\Api\Data\PaymentTokenInterface $entity The payment token. + * @param \Magento\Vault\Api\Data\PaymentTokenInterface $paymentToken The payment token. * @return \Magento\Vault\Api\Data\PaymentTokenInterface Saved payment token data. */ public function save(Data\PaymentTokenInterface $paymentToken) diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget.php b/app/code/Magento/Widget/Block/Adminhtml/Widget.php index 33e6109b769db..dad318f163b4b 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget.php @@ -16,8 +16,6 @@ class Widget extends \Magento\Backend\Block\Widget\Form\Container { /** * @inheritdoc - * - * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ protected function _construct() { diff --git a/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php b/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php index d7e3bf94f548f..6c0aef9e67186 100644 --- a/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php +++ b/app/code/Magento/Widget/Model/ResourceModel/Layout/Update.php @@ -51,7 +51,9 @@ protected function _construct() * @param string $handle * @param \Magento\Framework\View\Design\ThemeInterface $theme * @param \Magento\Framework\App\ScopeInterface $store + * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function fetchUpdatesByHandle( $handle, @@ -69,14 +71,16 @@ public function fetchUpdatesByHandle( $this->layoutUpdateCache[$cacheKey][$layout['handle']] .= $layout['xml']; } } - return isset($this->layoutUpdateCache[$cacheKey][$handle]) ? $this->layoutUpdateCache[$cacheKey][$handle] : ''; + return $this->layoutUpdateCache[$cacheKey][$handle] ?? ''; } /** * Get select to fetch updates by handle * * @param bool $loadAllUpdates + * * @return \Magento\Framework\DB\Select + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getFetchUpdatesByHandleSelect($loadAllUpdates = false) { diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml index 7955f4ec29e55..642ad6a268201 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml @@ -7,55 +7,62 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCreateProductsListWidgetActionGroup"> - <arguments> - <argument name="widget"/> - </arguments> - <amOnPage url="{{AdminDashboardPage.url}}" stepKey="amOnAdminDashboard"/> - <click selector="{{AdminMenuSection.content}}" stepKey="clickContent"/> - <waitForLoadingMaskToDisappear stepKey="waitForWidgets" /> - <click selector="{{AdminMenuSection.widgets}}" stepKey="clickWidgets"/> - <waitForPageLoad stepKey="waitForWidgetsLoad"/> - <click selector="{{AdminGridMainControls.add}}" stepKey="addNewWidget"/> - <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widget.type}}" stepKey="setWidgetType"/> - <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widget.design_theme}}" stepKey="setWidgetDesignTheme"/> - <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> - <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widget.name}}" stepKey="fillTitle"/> - <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" userInput="{{widget.store_ids[0]}}" stepKey="setWidgetStoreIds"/> - <click selector="{{AdminNewWidgetSection.addLayoutUpdate}}" stepKey="clickAddLayoutUpdate"/> - <selectOption selector="{{AdminNewWidgetSection.selectDisplayOn}}" userInput="{{widget.display_on}}" stepKey="setDisplayOn"/> - <waitForAjaxLoad stepKey="waitForLoad"/> - <selectOption selector="{{AdminNewWidgetSection.selectContainer}}" userInput="{{widget.container}}" stepKey="setContainer"/> - <waitForAjaxLoad stepKey="waitForPageLoad"/> - <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <click selector="{{AdminNewWidgetSection.widgetOptions}}" stepKey="clickWidgetOptions"/> - <click selector="{{AdminNewWidgetSection.addNewCondition}}" stepKey="clickAddNewCondition"/> - <selectOption selector="{{AdminNewWidgetSection.selectCondition}}" userInput="{{widget.condition}}" stepKey="selectCondition"/> - <waitForElement selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="waitRuleParameter"/> - <click selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="clickRuleParameter"/> - <click selector="{{AdminNewWidgetSection.openChooser}}" stepKey="clickChooser"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> - <click selector="{{AdminNewWidgetSection.selectAll}}" stepKey="clickSelectAll"/> - <click selector="{{AdminNewWidgetSection.applyParameter}}" stepKey="clickApplyRuleParameter"/> - <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> - <waitForPageLoad stepKey="waitForSaveLoad"/> - <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> - </actionGroup> - <actionGroup name="AdminDeleteWidgetActionGroup"> - <arguments> - <argument name="widget"/> - </arguments> - <amOnPage url="{{AdminWidgetsPage.url}}" stepKey="amOnAdmin"/> - <waitForPageLoad stepKey="waitWidgetsLoad"/> - <fillField selector="{{AdminWidgetsSection.widgetTitleSearch}}" userInput="{{widget.name}}" stepKey="fillTitle"/> - <click selector="{{AdminWidgetsSection.searchButton}}" stepKey="clickContinue"/> - <click selector="{{AdminWidgetsSection.searchResult}}" stepKey="clickSearchResult"/> - <waitForPageLoad stepKey="waitForResultLoad"/> - <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickDelete"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> - <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete"/> - <waitForPageLoad stepKey="waitForDeleteLoad"/> - <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been deleted" stepKey="seeSuccess"/> - </actionGroup> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateWidgetActionGroup"> + <arguments> + <argument name="widget"/> + </arguments> + <amOnPage url="{{AdminNewWidgetPage.url}}" stepKey="amOnAdminNewWidgetPage"/> + <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widget.type}}" stepKey="setWidgetType"/> + <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widget.design_theme}}" stepKey="setWidgetDesignTheme"/> + <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> + <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widget.name}}" stepKey="fillTitle"/> + <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" userInput="{{widget.store_ids[0]}}" stepKey="setWidgetStoreIds"/> + <click selector="{{AdminNewWidgetSection.addLayoutUpdate}}" stepKey="clickAddLayoutUpdate"/> + <selectOption selector="{{AdminNewWidgetSection.selectDisplayOn}}" userInput="{{widget.display_on}}" stepKey="setDisplayOn"/> + <waitForAjaxLoad stepKey="waitForLoad"/> + <selectOption selector="{{AdminNewWidgetSection.selectContainer}}" userInput="{{widget.container}}" stepKey="setContainer"/> + <waitForAjaxLoad stepKey="waitForPageLoad"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewWidgetSection.widgetOptions}}" stepKey="clickWidgetOptions"/> + </actionGroup> + + <!--Create Product List Widget--> + <actionGroup name="AdminCreateProductsListWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <click selector="{{AdminNewWidgetSection.addNewCondition}}" stepKey="clickAddNewCondition"/> + <selectOption selector="{{AdminNewWidgetSection.selectCondition}}" userInput="{{widget.condition}}" stepKey="selectCondition"/> + <waitForElement selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="waitRuleParameter"/> + <click selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="clickRuleParameter"/> + <click selector="{{AdminNewWidgetSection.openChooser}}" stepKey="clickChooser"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminNewWidgetSection.selectAll}}" stepKey="clickSelectAll"/> + <click selector="{{AdminNewWidgetSection.applyParameter}}" stepKey="clickApplyRuleParameter"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> + + <!--Create Dynamic Block Rotate Widget--> + <actionGroup name="AdminCreateDynamicBlocksRotatorWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <selectOption selector="{{AdminNewWidgetSection.displayMode}}" userInput="{{widget.display_mode}}" stepKey="selectDisplayMode"/> + <selectOption selector="{{AdminNewWidgetSection.restrictTypes}}" userInput="{{widget.restrict_type}}" stepKey="selectRestrictType"/> + <click selector="{{AdminNewWidgetSection.saveAndContinue}}" stepKey="clickSaveWidget"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> + + <actionGroup name="AdminDeleteWidgetActionGroup"> + <arguments> + <argument name="widget"/> + </arguments> + <amOnPage url="{{AdminWidgetsPage.url}}" stepKey="amOnAdmin"/> + <waitForPageLoad stepKey="waitWidgetsLoad"/> + <fillField selector="{{AdminWidgetsSection.widgetTitleSearch}}" userInput="{{widget.name}}" stepKey="fillTitle"/> + <click selector="{{AdminWidgetsSection.searchButton}}" stepKey="clickContinue"/> + <click selector="{{AdminWidgetsSection.searchResult}}" stepKey="clickSearchResult"/> + <waitForPageLoad stepKey="waitForResultLoad"/> + <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickDelete"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete"/> + <waitForPageLoad stepKey="waitForDeleteLoad"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been deleted" stepKey="seeSuccess"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml index 26864c60b6494..27222298408de 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductsListWidget" type="widget"> <data key="type">Catalog Products List</data> <data key="design_theme">Magento Luma</data> @@ -19,4 +19,17 @@ <data key="display_on">All Pages</data> <data key="container">Main Content Area</data> </entity> + <entity name="DynamicBlocksRotatorWidget" type="widget"> + <data key="type">Dynamic Blocks Rotator</data> + <data key="design_theme">Magento Luma</data> + <data key="name" unique="suffix">TestBannerWidget</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> + <data key="condition">SKU</data> + <data key="display_on">All Pages</data> + <data key="container">Main Content Area</data> + <data key="display_mode">Cart Price Rule Related</data> + <data key="restrict_type">Header</data> + </entity> </entities> diff --git a/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml b/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml index 8eb0a5f65318e..d495a36f68d0a 100644 --- a/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml +++ b/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml @@ -7,8 +7,8 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="AdminNewWidgetPage" url="admin/admin/widget_instance/new/" area="admin" module="Magento_Widget"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminNewWidgetPage" url="admin/widget_instance/new/" area="admin" module="Magento_Widget"> <section name="AdminNewWidgetSection"/> </page> </pages> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index 38b4df335ea83..aea74b573ce11 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewWidgetSection"> <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> @@ -25,5 +25,8 @@ <element name="applyParameter" type="button" selector=".rule-param-apply"/> <element name="openChooser" type="button" selector=".rule-chooser-trigger"/> <element name="selectAll" type="checkbox" selector=".admin__control-checkbox"/> + <element name="displayMode" type="select" selector="select[id*='display_mode']"/> + <element name="restrictTypes" type="select" selector="select[id*='types']"/> + <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml new file mode 100644 index 0000000000000..4407991ff5a93 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ProductsListWidgetTest"> + <annotations> + <features value="Widget"/> + <stories value="Products list widget"/> + <title value="Admin should be able to set Products List Widget"/> + <description value="Admin should be able to set Products List Widget"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-97041"/> + <group value="Widget"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <conditionalClick selector="{{CmsPagesPageActionsSection.clearAllButton}}" dependentSelector="{{CmsPagesPageActionsSection.activeFilters}}" stepKey="clickToResetFilter" visible="true"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + </after> + <!-- Create a CMS page containing the Products List widget --> + <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnCmsList"/> + <waitForPageLoad stepKey="waitForCmsList"/> + <click selector="{{CmsPagesPageActionsSection.addNewPageButton}}" stepKey="clickAddNewPageButton"/> + <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_newDefaultCmsPage.title}}" stepKey="fillPageTitle"/> + <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="expandContentSection"/> + <waitForPageLoad stepKey="waitForContentSection"/> + <click selector="{{CmsWYSIWYGSection.InsertWidgetBtn}}" stepKey="clickInsertWidgetButton"/> + <waitForPageLoad stepKey="waitForSlideOut"/> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Catalog Products List" stepKey="selectWidgetType"/> + <waitForPageLoad stepKey="waitForWidgetOptions"/> + <click selector="{{AdminNewWidgetSection.addNewCondition}}" stepKey="clickAddNewCondition"/> + <selectOption selector="{{AdminNewWidgetSection.selectCondition}}" userInput="Magento\CatalogWidget\Model\Rule\Condition\Product|category_ids" stepKey="selectCondition"/> + <waitForElement selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="waitRuleParameter"/> + <click selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="clickRuleParameter"/> + <click selector="{{AdminNewWidgetSection.openChooser}}" stepKey="clickChooser"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" stepKey="clickCategoryToEditInitial"/> + <click selector="{{AdminNewWidgetSection.applyParameter}}" stepKey="clickApplyRuleParameter"/> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidget"/> + <click selector="{{CmsNewPagePageActionsSection.expandSplitButton}}" stepKey="expandSplitBtn"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndClose}}" stepKey="clickSaveAndClose"/> + <waitForPageLoad stepKey="waitForCmsList2"/> + <see userInput="You saved the page." stepKey="seeSuccessMessage"/> + <!-- Verify CMS page on storefront --> + <waitForElementVisible selector="{{CmsPagesPageActionsSection.select(_newDefaultCmsPage.title)}}" stepKey="waitForCMSPageListItem" /> + <click selector="{{CmsPagesPageActionsSection.select(_newDefaultCmsPage.title)}}" stepKey="clickSelect" /> + <waitForElementVisible selector="{{CmsPagesPageActionsSection.edit(_newDefaultCmsPage.title)}}" stepKey="waitForEditLink" /> + <click selector="{{CmsPagesPageActionsSection.preview(_newDefaultCmsPage.title)}}" stepKey="clickEdit" /> + <waitForPageLoad stepKey="waitForCMSPage"/> + <seeInTitle userInput="{{_newDefaultCmsPage.title}}" stepKey="seePageTitle"/> + <see userInput="{{_newDefaultCmsPage.title}}" stepKey="seeProduct"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/WishlistAnalytics/README.md b/app/code/Magento/WishlistAnalytics/README.md index 999fc835626da..1ad889598297c 100644 --- a/app/code/Magento/WishlistAnalytics/README.md +++ b/app/code/Magento/WishlistAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_WishlistAnalytics module -The Magento_WishlistAnalytics module configures data definitions for a data collection related to the Wishlist module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_WishlistAnalytics module configures data definitions for a data collection related to the Wishlist module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less index ba28108326006..d0b17b3439d66 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less @@ -15,9 +15,9 @@ @menu__background-color: @color-very-dark-grayish-orange; -@menu-logo__padding-bottom: 2.2rem; +@menu-logo__padding-bottom: 1.7rem; @menu-logo__outer-size: @menu-logo__padding-top + @menu-logo-img__height + @menu-logo__padding-bottom; -@menu-logo__padding-top: 1.2rem; +@menu-logo__padding-top: 1.7rem; @menu-logo-img__height: 4.1rem; @menu-logo-img__width: 3.5rem; diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less index 80bebb22a9043..ad407160034ac 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less @@ -7,13 +7,13 @@ // Main elements -> Actions bar -> Store Switcher // _____________________________________________ -// ToDo UI: Consist old styles, should be changed with new design +// ToDo UI: Consist old styles, should be changed with new design .store-switcher { - color: @text__color; // ToDo UI: Delete with admin scope + color: @text__color; float: left; font-size: round(@font-size__base - .1rem, 1); - margin-top: .7rem; + margin-top: .59rem; .admin__action-dropdown { background-color: @page-main-actions__background-color; @@ -47,8 +47,8 @@ width: 7px; } &::-webkit-scrollbar-thumb { - border-radius: 4px; background-color: rgba(0, 0, 0, .5); + border-radius: 4px; } li { @@ -239,7 +239,6 @@ .store-switcher-label { display: inline-block; - margin-top: @indent__s; } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less index a80cc9a5163f8..c8f2530df22e0 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less @@ -3,14 +3,77 @@ // * See COPYING.txt for license details. // */ -// General rule hides group legend and shows first field label instead -// in app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less -// This must be reset for Customer Address page -.address-item-edit-content { +.customer_form_areas_address_address_customer_address_update_modal_update_customer_address_form_loader { .admin__field { - legend { - &.admin__field-label { - opacity: 1; + .admin__field { + .admin__field-label { + background: none; + } + } + } +} + +.customer-address-form { + + *, + *:after, + *:before { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + } + + address { + font-style: normal; + } + + .customer-default-address-wrapper { + align-items: flex-start; + display: flex; + float: left; + position: relative; + width: 50%; + + .action-additional { + margin: 2px 0 0 2px; + } + } + + .edit-default-billing-address-button, + .edit-default-shipping-address-button { + position: absolute; + } + + .edit-default-billing-address-button { + left: 210px; + } + + .edit-default-shipping-address-button { + left: 230px; + } + + .customer_form_areas_address_address_customer_address_listing { + clear: both; + } + + .add-new-address-button { + clear: both; + float: right; + margin-bottom: 30px; + position: relative; + } + + .address-information { + float: left; + margin-bottom: 20px; + + address { + float: left; + + .address_caption { + font-size: 18px; + font-weight: bold; + margin-bottom: 16px; } } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less index 17be2ca706076..08606402f7a0e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less @@ -34,7 +34,7 @@ .admin__field-control { direction: rtl; display: inline-block; - margin: -4px 0 0; + margin: -1px 0 0; unicode-bidi: bidi-override; vertical-align: top; width: 125px; diff --git a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less index 1cd867efdd13b..554b6394a1094 100644 --- a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less @@ -68,12 +68,14 @@ a { color: @color-gray85; + cursor: move; display: block; float: left; text-decoration: none; } a:last-child { + cursor: pointer; float: right; } } diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less index c535047e37682..0049022204619 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less @@ -93,7 +93,7 @@ } } -// ToDo UI: Only right tooltip arrow is styled, if we will neeed more need to add another ones +// ToDo UI: Only right tooltip arrow is styled, if we will need more need to add another ones .tooltip { &.top { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less index 7b99d4f136d21..4c364bed688bc 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less @@ -179,7 +179,7 @@ .admin__action-multiselect-search-label { display: block; font-size: 1.5rem; - height: 1em; + height: 1.3em; overflow: hidden; position: absolute; right: 2.2rem; @@ -199,8 +199,8 @@ .admin__action-multiselect-empty-area { color: @color-gray65-almost; - padding-top: 20px; padding-bottom: 20px; + padding-top: 20px; text-align: center; vertical-align: middle; } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index fe9cbe9b9771d..f4eead6ed4ebc 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -121,6 +121,9 @@ > .admin__field-control { #mix-grid .column(@field-control-grid__column, @field-grid__columns); + input[type="checkbox"] { + margin-top: 1.2rem; + } } > .admin__field-label { @@ -685,9 +688,11 @@ margin: 0; opacity: 1; position: static; - text-align: left; } } + & > .admin__field-label { + text-align: left; + } &:nth-child(n + 2) { &:not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date) { diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index bdac02bbe2530..dde4dafeeed23 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -1188,7 +1188,7 @@ } // - // Collapsable fieldset-wrapper + // collapsible fieldset-wrapper // -------------------------------------- // Fieldset styles in another fieldset @@ -3845,6 +3845,26 @@ .rule-param-edit .element { display: inline; + position: relative; + } + + .rule-param-edit .element input.input-date, + .rule-param-edit .element input.input-date[readonly] { + background-color: @color-white; + min-width: 140px; + width: 140px !important; + cursor: pointer; + text-align: center; + opacity: 1; + margin-right: 10px; + padding-right: 40px; + + + .ui-datepicker-trigger { + position: absolute; + width: 140px; + text-align: right; + left: 0; + } } .rule-param-edit .element .addafter { diff --git a/app/design/adminhtml/Magento/backend/web/js/theme.js b/app/design/adminhtml/Magento/backend/web/js/theme.js index a2511e27e9f67..8e3b89dcf7e4e 100644 --- a/app/design/adminhtml/Magento/backend/web/js/theme.js +++ b/app/design/adminhtml/Magento/backend/web/js/theme.js @@ -599,7 +599,7 @@ define('js/theme', [ ], function ($, keyboardHandler) { 'use strict'; - /* @TODO refactor collapsable as widget and avoid logic binding with such a general selectors */ + /* @TODO refactor collapsible as widget and avoid logic binding with such a general selectors */ $('.collapse').collapsable(); $.each($('.entry-edit'), function (i, entry) { diff --git a/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less index b70bab6f320d8..8c1ec09afec1e 100644 --- a/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less @@ -9,9 +9,9 @@ @braintree-input-border__color: @color-gray76; -@braintree-error__color: @color-red10; -@braintree-focus__color: @color-blue2; -@braintree-success__color: @color-dark-green1; +@braintree-error__color: @message-error__color; +@braintree-focus__color: @theme__color__primary-alt; +@braintree-success__color: @message-success__color; @braintree-paypal-icon__height: 16px; @braintree-paypal-icon__width: 16px; diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less index 2468d2a3104e4..8e7f36389c417 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less @@ -65,6 +65,11 @@ .products.wrapper ~ .toolbar & { display: none; } + + .sorter-action { + position: relative; + top: -2px; + } } .sorter-options { diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less index 301517c6d0cd6..4792cb7b17924 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less @@ -16,7 +16,7 @@ @checkout-progress-bar-item__color: @primary__color; @checkout-progress-bar-item__margin: @indent__s; @checkout-progress-bar-item__width: 185px; -@checkout-progress-bar-item__active__background-color: @color-orange-red1; +@checkout-progress-bar-item__active__background-color: @active__color; @checkout-progress-bar-item__complete__color: @link__color; @checkout-progress-bar-item-element__height: @checkout-progress-bar-item-element__width; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less index 158cb0ebc0ed1..af6127fd2ca9f 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less @@ -22,7 +22,7 @@ @checkout-shipping-item__width: 100%/3; @checkout-shipping-item-tablet__width: 100%/2; @checkout-shipping-item-mobile__width: 100%; -@checkout-shipping-item__active__border-color: @color-orange-red1; +@checkout-shipping-item__active__border-color: @active__color; @checkout-shipping-item-icon__selected__height: 27px; @checkout-shipping-item-icon__selected__width: 29px; diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less index e2fd226a54fe3..9df59ca5dac92 100644 --- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less @@ -11,7 +11,7 @@ @account-nav-color: false; @account-nav-current-border: 3px solid transparent; -@account-nav-current-border-color: @color-orange-red1; +@account-nav-current-border-color: @active__color; @account-nav-current-color: false; @account-nav-current-font-weight: @font-weight__semibold; diff --git a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less index 96648f504af80..46f9661d8281d 100644 --- a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less @@ -47,7 +47,7 @@ } .error-block { - color: @color-red10; + color: @error__color; .error-label { font-weight: @font-weight__bold; @@ -222,7 +222,7 @@ } .error-description { - color: @color-red10; + color: @error__color; font-weight: @font-weight__regular; margin-bottom: @indent__s; margin-top: -@indent__s; diff --git a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less index f42efa3569598..28aa3f187e95c 100644 --- a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less @@ -18,7 +18,7 @@ @swatch-option__selected__border: @swatch-option__hover__border; @swatch-option__selected__color: @swatch-option__hover__color; -@swatch-option__selected__outline: 2px solid @color-orange-red1; +@swatch-option__selected__outline: 2px solid @active__color; @swatch-option__disabled__background: @color-red10; @@ -38,7 +38,7 @@ // Image and Color swatch @img-color-swatch-option__hover__border: @swatch-option__hover__border; -@img-color-swatch-option__hover__outline: 2px solid #e00; +@img-color-swatch-option__hover__outline: 2px solid darken(@active__color, 12%); // Tooltip @swatch-option-tooltip__background: @color-white; @@ -54,7 +54,7 @@ @swatch-option-tooltip-layered-title__color: @swatch-option-tooltip-title__color; // Layered Features -@swatch-option-link-layered__focus__box-shadow: 0 0 3px 1px @color-sky-blue1; +@swatch-option-link-layered__focus__box-shadow: 0 0 3px 1px @focus__color; // // Common @@ -214,7 +214,6 @@ .lib-css(color, @swatch-option-tooltip__color); display: none; max-height: 100%; - max-width: 140px; min-height: 20px; min-width: 20px; padding: @indent__xs; @@ -279,7 +278,6 @@ .lib-css(color, @swatch-option-tooltip-title__color); display: block; max-height: 200px; - max-width: 140px; min-height: 20px; overflow: hidden; text-align: center; diff --git a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less index 8df5556e97721..b314bcf5b3473 100644 --- a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less @@ -296,10 +296,6 @@ box-sizing: border-box; width: 100%; } - - .ie11 & { - height: 100%; - } } .navigation ul { diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index 5f24c0db8cc88..74dfd48d87a87 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -253,7 +253,7 @@ } // -// Marging for blocks & widgets +// Margin for blocks & widgets // --------------------------------------------- & when (@media-common = true) { diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 6bf766b7400a7..3cb3d4af53189 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -42,7 +42,7 @@ .products-grid & { display: inline-block; - width: 100%/2; + width: 100%/2.04; } &:extend(.abs-add-box-sizing all); @@ -363,7 +363,7 @@ .products-grid { .product-item { margin-bottom: @indent__base; - width: 100%/3; + width: 100%/3.04; } } @@ -374,7 +374,7 @@ .page-products.page-layout-3columns { .products-grid { .product-item { - width: 100%/3; + width: 100%/3.04; } } } @@ -388,7 +388,7 @@ .page-products { .products-grid { .product-item { - width: 100%/3; + width: 100%/3.04; } } } @@ -443,7 +443,7 @@ .product-item { margin-left: calc(~'(100% - 4 * 24.439%) / 3'); padding: 5px; - width: 24.439%; + width: 24.09%; &:nth-child(4n + 1) { margin-left: 0; diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less index 0997b9739125d..6bddc46003cbf 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less @@ -38,7 +38,10 @@ .lib-css(box-shadow, @button__shadow); border-radius: 3px; } - + .sorter-action { + position: relative; + top: -2px; + } &-amount { left: 0; line-height: @toolbar-mode-icon-font-size + 2; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index 58582d344e75a..5aaf0cd02fab9 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -97,6 +97,7 @@ .field { .radio { float: left; + margin-top: 4px; } .radio { @@ -145,12 +146,6 @@ } &:extend(.abs-adjustment-incl-excl-tax all); - - .action { - &.multicheckout { - color: @color-blue2; - } - } } // Totals block diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less index 9576004421e48..b9b223f44021a 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less @@ -110,9 +110,9 @@ @_toggle-selector: ~'.action.showcart', @_options-selector: ~'.block-minicart', @_dropdown-list-width: 320px, - @_dropdown-list-position-right: 0, + @_dropdown-list-position-right: -10px, @_dropdown-list-pointer-position: right, - @_dropdown-list-pointer-position-left-right: 26px, + @_dropdown-list-pointer-position-left-right: 12px, @_dropdown-list-z-index: 101, @_dropdown-toggle-icon-content: @icon-cart, @_dropdown-toggle-active-icon-content: @icon-cart, @@ -153,17 +153,17 @@ .action { &.close { - height: 40px; + height: 30px; position: absolute; right: 0; top: 0; - width: 40px; + width: 25px; .lib-button-reset(); .lib-button-icon( @icon-remove, @_icon-font-color: @minicart-icons-color, - @_icon-font-size: 16px, - @_icon-font-line-height: 16px, + @_icon-font-size: 14px, + @_icon-font-line-height: 14px, @_icon-font-text-hide: true ); } @@ -415,7 +415,6 @@ margin-left: 13px; .block-minicart { - right: -15px; width: 390px; } } diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less index 5ecc4d4713bf1..9bad9518f5724 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less @@ -137,6 +137,10 @@ } .product-item { + .product-item-details { + &:extend(.abs-add-clearfix all); + } + .product-item-inner { display: table; margin: 0 0 @indent__s; @@ -166,6 +170,10 @@ } } } + + .message { + margin-top: 10px; + } } .actions-toolbar { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less index e2378eeeef0c1..ad1ac36237ca4 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less @@ -18,7 +18,7 @@ @checkout-progress-bar-item__transition: background .3s; @checkout-progress-bar-item__width: 185px; -@checkout-progress-bar-item__active__background-color: @color-orange-red1; +@checkout-progress-bar-item__active__background-color: @active__color; @checkout-progress-bar-item__active__color: @primary__color; @checkout-progress-bar-item__active__font-weight: @font-weight__semibold; @checkout-progress-bar-item__complete__color: @link__color; @@ -32,7 +32,7 @@ @checkout-progress-bar-item-element-inner__color: @primary__color; @checkout-progress-bar-item-element-inner__height: @checkout-progress-bar-item-element-inner__width; @checkout-progress-bar-item-element-inner__width: @checkout-progress-bar-item-element__width - ( @checkout-progress-bar-item-element-outer-radius__width*2 ); -@checkout-progress-bar-item-element-inner__active__border-color: @color-orange-red1; +@checkout-progress-bar-item-element-inner__active__border-color: @active__color; @checkout-progress-bar-item-element-inner__active__content: @icon-checkmark; @checkout-progress-bar-item-element-outer-radius__width: 6px; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less index d0ce87beb6ad3..d0edf245dc5b8 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less @@ -22,7 +22,7 @@ @checkout-shipping-item__width: 100%/3; @checkout-shipping-item-tablet__width: 100%/2; @checkout-shipping-item-mobile__width: 100%; -@checkout-shipping-item__active__border-color: @color-orange-red1; +@checkout-shipping-item__active__border-color: @active__color; @checkout-shipping-item-icon__selected__height: 27px; @checkout-shipping-item-icon__selected__width: 29px; diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index 5418d836fc262..2e7856d390bd0 100755 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -334,13 +334,17 @@ } } - .order-products-toolbar { + .order-products-toolbar, + .customer-addresses-toolbar { position: relative; .toolbar-amount { position: relative; text-align: center; } + .pages { + position: relative; + } } } diff --git a/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less index 388ec32b4fc5a..41e6da39e1ef1 100644 --- a/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less @@ -12,7 +12,7 @@ @gift-item-block__border-color: @color-gray-light5; @gift-item-block__border-width: @border-width__base; -@gift-item-block-title__color: @color-blue1; +@gift-item-block-title__color: @link__color; @gift-item-block-title-icon__content: @icon-down; @gift-item-block-title-icon__active__content: @icon-up; @gift-item-block-title-icon__color: @color-gray52; @@ -60,8 +60,8 @@ } .gift-summary { - position: relative; margin-top: @indent__s; + position: relative; .actions-toolbar { > .secondary { diff --git a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less index 48461892fa631..76baec87e0054 100644 --- a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less @@ -32,7 +32,7 @@ &[data-count]:after { .lib-css(color, @color-white); - background: @color-orange-red1; + background: @theme__color__secondary; border-radius: 2px; content: attr(data-count); display: inline-block; diff --git a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less index 7662c60734a1b..ed6b53727da52 100644 --- a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less @@ -48,7 +48,7 @@ } .error-block { - color: @color-red10; + color: @error__color; .error-label { font-weight: @font-weight__bold; @@ -230,7 +230,7 @@ } .error-description { - color: @color-red10; + color: @error__color; font-weight: @font-weight__regular; margin-bottom: @indent__s; margin-top: -@indent__s; diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less index bafe1be57ee35..2d49042198ff4 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less @@ -312,6 +312,23 @@ } } } + .page-header { + .switcher { + .options { + ul.dropdown { + right: 0; + &:before { + left: auto; + right: 10px; + } + &:after { + left: auto; + right: 9px; + } + } + } + } + } // // Widgets @@ -627,10 +644,6 @@ box-sizing: border-box; width: 100%; } - - .ie11 & { - height: 100%; - } } .page-footer { diff --git a/app/design/frontend/Magento/luma/web/css/source/_extends.less b/app/design/frontend/Magento/luma/web/css/source/_extends.less index ad677b45e52db..a88ecbf5057bb 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -277,7 +277,7 @@ } // -// Marging for blocks & widgets +// Margin for blocks & widgets // --------------------------------------------- & when (@media-common = true) { @@ -1863,7 +1863,7 @@ > .title { strong { - color: @color-blue1; + color: @link__color; font-weight: @font-weight__regular; } } diff --git a/app/design/frontend/Magento/luma/web/css/source/_theme.less b/app/design/frontend/Magento/luma/web/css/source/_theme.less index 957e622d6a0bb..114fb7b817267 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_theme.less +++ b/app/design/frontend/Magento/luma/web/css/source/_theme.less @@ -28,7 +28,7 @@ @h3__margin-top: @indent__base; // Links -@link__color: @color-blue2; +@link__color: @theme__color__primary-alt; // Focus @focus__color: @color-blue3; @@ -60,14 +60,9 @@ @navigation__background: @color-gray94; @navigation-level0-item__active__color: @primary__color; -@navigation-level0-item__active__border-color: @active__color; -@navigation-desktop-level0-item__active__border-color: @active__color; -@submenu-desktop-item__active__border-color: @active__color; -@submenu-item__active__border-color: @active__color; @submenu-item__active__color: @navigation-level0-item__active__color; - // Desktop navigation @navigation-desktop-level0-item__line-height: 47px; @submenu-desktop__font-weight: @font-weight__regular; @@ -225,7 +220,6 @@ @rating-icon__font-size: 16px; @rating-icon__letter-spacing: 2px; -@rating-icon__active__color: @active__color; // // Dropdowns @@ -252,7 +246,7 @@ @breadcrumbs-icon__font-margin: 0 @indent__s; @breadcrumbs-current__color: @color-gray-middle5; -@breadcrumbs-link__color: @color-blue2; +@breadcrumbs-link__color: @link__color; @breadcrumbs-link__visited__color: @breadcrumbs-link__color; @breadcrumbs-link__hover__color: @breadcrumbs-link__color; @breadcrumbs-link__active__color: @breadcrumbs-link__color; diff --git a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less index ae68fc81acd6e..3814341efd05a 100644 --- a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less @@ -82,7 +82,8 @@ .modal-slide { .action-close { - padding: @modal-slide-action-close__padding; + margin: 15px; + padding: 0; } .page-main-actions { diff --git a/app/etc/di.xml b/app/etc/di.xml index ceccee04a8bc4..6cf169c1d2277 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1749,4 +1749,11 @@ </argument> </arguments> </type> + <type name="Magento\Framework\App\ScopeResolverPool"> + <arguments> + <argument name="scopeResolvers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Framework\App\ScopeResolver</item> + </argument> + </arguments> + </type> </config> diff --git a/composer.json b/composer.json index 07aaa31f28fa4..62b3c95135669 100644 --- a/composer.json +++ b/composer.json @@ -84,12 +84,13 @@ "require-dev": { "friendsofphp/php-cs-fixer": "~2.13.0", "lusitanian/oauth": "~0.8.10", - "magento/magento2-functional-testing-framework": "2.3.11", + "magento/magento2-functional-testing-framework": "~2.3.12", "pdepend/pdepend": "2.5.2", "phpmd/phpmd": "@stable", "phpunit/phpunit": "~6.5.0", "sebastian/phpcpd": "~3.0.0", - "squizlabs/php_codesniffer": "3.3.1" + "squizlabs/php_codesniffer": "3.3.1", + "allure-framework/allure-phpunit": "~1.2.0" }, "suggest": { "ext-pcntl": "Need for run processes in parallel mode" @@ -167,6 +168,7 @@ "magento/module-cms-graph-ql": "*", "magento/module-grouped-import-export": "*", "magento/module-grouped-product": "*", + "magento/module-grouped-catalog-inventory": "*", "magento/module-grouped-product-graph-ql": "*", "magento/module-import-export": "*", "magento/module-indexer": "*", diff --git a/composer.lock b/composer.lock index f26b71f1cc392..d24dfd2b612fc 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": "93418bd14ad2d9f54f4059550ded7061", + "content-hash": "e2fcf8723503311ee9fea99dece55225", "packages": [ { "name": "braintree/braintree_php", @@ -257,16 +257,16 @@ }, { "name": "composer/composer", - "version": "1.7.2", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2" + "reference": "d8aef3af866b28786ce9b8647e52c42496436669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/576aab9b5abb2ed11a1c52353a759363216a4ad2", - "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2", + "url": "https://api.github.com/repos/composer/composer/zipball/d8aef3af866b28786ce9b8647e52c42496436669", + "reference": "d8aef3af866b28786ce9b8647e52c42496436669", "shasum": "" }, "require": { @@ -302,7 +302,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -333,7 +333,7 @@ "dependency", "package" ], - "time": "2018-08-16T14:57:12+00:00" + "time": "2018-12-03T09:31:16+00:00" }, { "name": "composer/semver", @@ -399,16 +399,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b" + "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/cb17687e9f936acd7e7245ad3890f953770dec1b", - "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", "shasum": "" }, "require": { @@ -456,20 +456,20 @@ "spdx", "validator" ], - "time": "2018-04-30T10:33:04+00:00" + "time": "2018-11-01T09:45:54+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "dc523135366eb68f22268d069ea7749486458562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", + "reference": "dc523135366eb68f22268d069ea7749486458562", "shasum": "" }, "require": { @@ -500,7 +500,7 @@ "Xdebug", "performance" ], - "time": "2018-08-31T19:07:57+00:00" + "time": "2018-11-29T10:59:02+00:00" }, { "name": "container-interop/container-interop", @@ -919,16 +919,16 @@ }, { "name": "monolog/monolog", - "version": "1.23.0", + "version": "1.24.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", "shasum": "" }, "require": { @@ -993,7 +993,7 @@ "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "time": "2018-11-05T09:00:11+00:00" }, { "name": "oyejorge/less.php", @@ -1104,16 +1104,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8" + "reference": "5e280b50cdaf8da4cc4810e0847a9618be29703d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/7b73005be3c224f12c47bd75a23ce24b762e47e8", - "reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/5e280b50cdaf8da4cc4810e0847a9618be29703d", + "reference": "5e280b50cdaf8da4cc4810e0847a9618be29703d", "shasum": "" }, "require": { @@ -1182,28 +1182,33 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2018-09-22T03:59:58+00:00" + "time": "2018-11-29T22:33:39+00:00" }, { "name": "pelago/emogrifier", - "version": "v2.0.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/MyIntervals/emogrifier.git", - "reference": "8babf8ddbf348f26b29674e2f84db66ff7e3d95e" + "reference": "8ee7fb5ad772915451ed3415c1992bd3697d4983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/8babf8ddbf348f26b29674e2f84db66ff7e3d95e", - "reference": "8babf8ddbf348f26b29674e2f84db66ff7e3d95e", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/8ee7fb5ad772915451ed3415c1992bd3697d4983", + "reference": "8ee7fb5ad772915451ed3415c1992bd3697d4983", "shasum": "" }, "require": { - "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0" + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "symfony/css-selector": "^3.4.0 || ^4.0.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^2.2.0", + "phpmd/phpmd": "^2.6.0", "phpunit/phpunit": "^4.8.0", - "squizlabs/php_codesniffer": "^3.1.0" + "squizlabs/php_codesniffer": "^3.3.2" }, "type": "library", "extra": { @@ -1213,7 +1218,7 @@ }, "autoload": { "psr-4": { - "Pelago\\": "Classes/" + "Pelago\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1231,10 +1236,6 @@ { "name": "Jaime Prado" }, - { - "name": "Roman Ožana", - "email": "ozana@omdesign.cz" - }, { "name": "Oliver Klee", "email": "github@oliverklee.de" @@ -1242,6 +1243,10 @@ { "name": "Zoli Szabó", "email": "zoli.szabo+github@gmail.com" + }, + { + "name": "Jake Hotson", + "email": "jake@qzdesign.co.uk" } ], "description": "Converts CSS styles into inline style attributes in your HTML code", @@ -1251,7 +1256,7 @@ "email", "pre-processing" ], - "time": "2018-01-05T23:30:21+00:00" + "time": "2018-12-10T10:36:30+00:00" }, { "name": "php-amqplib/php-amqplib", @@ -1375,16 +1380,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.11", + "version": "2.0.12", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "7053f06f91b3de78e143d430e55a8f7889efc08b" + "reference": "8814dc7841db159daed0b32c2b08fb7e03c6afe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7053f06f91b3de78e143d430e55a8f7889efc08b", - "reference": "7053f06f91b3de78e143d430e55a8f7889efc08b", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/8814dc7841db159daed0b32c2b08fb7e03c6afe7", + "reference": "8814dc7841db159daed0b32c2b08fb7e03c6afe7", "shasum": "" }, "require": { @@ -1463,7 +1468,7 @@ "x.509", "x509" ], - "time": "2018-04-15T16:55:05+00:00" + "time": "2018-11-04T05:45:48+00:00" }, { "name": "psr/container", @@ -1566,16 +1571,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -1609,7 +1614,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "ramsey/uuid", @@ -1834,16 +1839,16 @@ }, { "name": "symfony/console", - "version": "v4.1.6", + "version": "v4.1.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" + "reference": "c74f4d1988dfcd8760273e53551694da32b056d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", - "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "url": "https://api.github.com/repos/symfony/console/zipball/c74f4d1988dfcd8760273e53551694da32b056d0", + "reference": "c74f4d1988dfcd8760273e53551694da32b056d0", "shasum": "" }, "require": { @@ -1898,20 +1903,73 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:15:46+00:00" + "time": "2018-11-26T14:00:40+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "aa9fa526ba1b2ec087ffdfb32753803d999fcfcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/aa9fa526ba1b2ec087ffdfb32753803d999fcfcd", + "reference": "aa9fa526ba1b2ec087ffdfb32753803d999fcfcd", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.6", + "version": "v4.1.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e" + "reference": "c4a3b5d70c05e5e7de4f22a3e840cdb173ccd7bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e", - "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c4a3b5d70c05e5e7de4f22a3e840cdb173ccd7bf", + "reference": "c4a3b5d70c05e5e7de4f22a3e840cdb173ccd7bf", "shasum": "" }, "require": { @@ -1961,20 +2019,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:10:45+00:00" + "time": "2018-12-01T08:51:37+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", - "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", "shasum": "" }, "require": { @@ -1984,7 +2042,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2011,20 +2069,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/finder", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", "shasum": "" }, "require": { @@ -2033,7 +2091,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2060,11 +2118,11 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -2122,16 +2180,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -2177,20 +2235,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/process", - "version": "v4.1.6", + "version": "v4.1.9", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" + "reference": "471f6e24172366a97365baaae588ddaafbba9b20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", - "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", + "url": "https://api.github.com/repos/symfony/process/zipball/471f6e24172366a97365baaae588ddaafbba9b20", + "reference": "471f6e24172366a97365baaae588ddaafbba9b20", "shasum": "" }, "require": { @@ -2226,7 +2284,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-20T16:14:00+00:00" }, { "name": "tedivm/jshrink", @@ -3012,16 +3070,16 @@ }, { "name": "zendframework/zend-filter", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9" + "reference": "875da9790e5cb16b9a12f41453d5f7c441452daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/7b997dbe79459f1652deccc8786d7407fb66caa9", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/875da9790e5cb16b9a12f41453d5f7c441452daf", + "reference": "875da9790e5cb16b9a12f41453d5f7c441452daf", "shasum": "" }, "require": { @@ -3034,12 +3092,14 @@ "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-crypt": "^3.2.1", "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", "zendframework/zend-uri": "^2.6" }, "suggest": { + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", @@ -3048,8 +3108,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\Filter", @@ -3071,25 +3131,25 @@ "filter", "zf" ], - "time": "2018-04-11T16:20:04+00:00" + "time": "2018-12-12T23:14:25+00:00" }, { "name": "zendframework/zend-form", - "version": "2.12.0", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-form.git", - "reference": "565fb4f4bb3e0dbeea0173c923c4a8be77de9441" + "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/565fb4f4bb3e0dbeea0173c923c4a8be77de9441", - "reference": "565fb4f4bb3e0dbeea0173c923c4a8be77de9441", + "url": "https://api.github.com/repos/zendframework/zend-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", + "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1", + "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", "zendframework/zend-inputfilter": "^2.8", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, @@ -3123,8 +3183,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" + "dev-master": "2.13.x-dev", + "dev-develop": "2.14.x-dev" }, "zf": { "component": "Zend\\Form", @@ -3149,7 +3209,7 @@ "form", "zf" ], - "time": "2018-05-16T18:49:44+00:00" + "time": "2018-12-11T22:51:29+00:00" }, { "name": "zendframework/zend-http", @@ -3334,16 +3394,16 @@ }, { "name": "zendframework/zend-inputfilter", - "version": "2.8.2", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "3f02179e014d9ef0faccda2ad6c65d38adc338d8" + "reference": "799ad48ed1666d3c62126fec73dd20453b3a9e4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/3f02179e014d9ef0faccda2ad6c65d38adc338d8", - "reference": "3f02179e014d9ef0faccda2ad6c65d38adc338d8", + "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/799ad48ed1666d3c62126fec73dd20453b3a9e4d", + "reference": "799ad48ed1666d3c62126fec73dd20453b3a9e4d", "shasum": "" }, "require": { @@ -3383,7 +3443,7 @@ "inputfilter", "zf" ], - "time": "2018-05-14T17:38:03+00:00" + "time": "2018-12-13T22:51:54+00:00" }, { "name": "zendframework/zend-json", @@ -3620,16 +3680,16 @@ }, { "name": "zendframework/zend-math", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-math.git", - "reference": "f4358090d5d23973121f1ed0b376184b66d9edec" + "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/f4358090d5d23973121f1ed0b376184b66d9edec", - "reference": "f4358090d5d23973121f1ed0b376184b66d9edec", + "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", + "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", "shasum": "" }, "require": { @@ -3666,7 +3726,7 @@ "math", "zf2" ], - "time": "2016-04-07T16:29:53+00:00" + "time": "2018-12-04T15:34:17+00:00" }, { "name": "zendframework/zend-mime", @@ -4355,16 +4415,16 @@ }, { "name": "zendframework/zend-validator", - "version": "2.10.2", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "38109ed7d8e46cfa71bccbe7e6ca80cdd035f8c9" + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/38109ed7d8e46cfa71bccbe7e6ca80cdd035f8c9", - "reference": "38109ed7d8e46cfa71bccbe7e6ca80cdd035f8c9", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", + "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", "shasum": "" }, "require": { @@ -4374,6 +4434,7 @@ }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0", "zendframework/zend-cache": "^2.6.1", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", @@ -4387,6 +4448,7 @@ "zendframework/zend-uri": "^2.5" }, "suggest": { + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", @@ -4399,8 +4461,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" }, "zf": { "component": "Zend\\Validator", @@ -4422,25 +4484,26 @@ "validator", "zf2" ], - "time": "2018-02-01T17:05:33+00:00" + "time": "2018-12-13T21:23:15+00:00" }, { "name": "zendframework/zend-view", - "version": "2.10.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-view.git", - "reference": "4478cc5dd960e2339d88b363ef99fa278700e80e" + "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/4478cc5dd960e2339d88b363ef99fa278700e80e", - "reference": "4478cc5dd960e2339d88b363ef99fa278700e80e", + "url": "https://api.github.com/repos/zendframework/zend-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", + "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", + "zendframework/zend-json": "^2.6.1", "zendframework/zend-loader": "^2.5", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, @@ -4456,10 +4519,9 @@ "zendframework/zend-filter": "^2.6.1", "zendframework/zend-http": "^2.5.4", "zendframework/zend-i18n": "^2.6", - "zendframework/zend-json": "^2.6.1", "zendframework/zend-log": "^2.7", "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7 || ^3.0", + "zendframework/zend-mvc": "^2.7.14 || ^3.0", "zendframework/zend-navigation": "^2.5", "zendframework/zend-paginator": "^2.5", "zendframework/zend-permissions-acl": "^2.6", @@ -4476,8 +4538,8 @@ "zendframework/zend-filter": "Zend\\Filter component", "zendframework/zend-http": "Zend\\Http component", "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json component", "zendframework/zend-mvc": "Zend\\Mvc component", + "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", "zendframework/zend-navigation": "Zend\\Navigation component", "zendframework/zend-paginator": "Zend\\Paginator component", "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", @@ -4509,22 +4571,22 @@ "view", "zf2" ], - "time": "2018-01-17T22:21:50+00:00" + "time": "2018-12-06T21:20:01+00:00" } ], "packages-dev": [ { "name": "allure-framework/allure-codeception", - "version": "1.2.7", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-codeception.git", - "reference": "48598f4b4603b50b663bfe977260113a40912131" + "reference": "9d31d781b3622b028f1f6210bc76ba88438bd518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", - "reference": "48598f4b4603b50b663bfe977260113a40912131", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/9d31d781b3622b028f1f6210bc76ba88438bd518", + "reference": "9d31d781b3622b028f1f6210bc76ba88438bd518", "shasum": "" }, "require": { @@ -4562,7 +4624,7 @@ "steps", "testing" ], - "time": "2018-03-07T11:18:27+00:00" + "time": "2018-12-18T19:47:23+00:00" }, { "name": "allure-framework/allure-php-api", @@ -4616,6 +4678,56 @@ ], "time": "2016-12-07T12:15:46+00:00" }, + { + "name": "allure-framework/allure-phpunit", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/allure-framework/allure-phpunit.git", + "reference": "45504aeba41304cf155a898fa9ac1aae79f4a089" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/allure-framework/allure-phpunit/zipball/45504aeba41304cf155a898fa9ac1aae79f4a089", + "reference": "45504aeba41304cf155a898fa9ac1aae79f4a089", + "shasum": "" + }, + "require": { + "allure-framework/allure-php-api": "~1.1.0", + "mikey179/vfsstream": "1.*", + "php": ">=7.0.0", + "phpunit/phpunit": ">=6.0.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Yandex": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" + } + ], + "description": "A PHPUnit adapter for Allure report.", + "homepage": "http://allure.qatools.ru/", + "keywords": [ + "allure", + "attachments", + "cases", + "phpunit", + "report", + "steps", + "testing" + ], + "time": "2017-11-03T13:08:21+00:00" + }, { "name": "behat/gherkin", "version": "v4.4.5", @@ -4804,20 +4916,20 @@ }, { "name": "consolidation/annotated-command", - "version": "2.9.1", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac" + "reference": "288593672a8ca9ead2c73a8bfbfa4737862bfd6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", - "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/288593672a8ca9ead2c73a8bfbfa4737862bfd6a", + "reference": "288593672a8ca9ead2c73a8bfbfa4737862bfd6a", "shasum": "" }, "require": { - "consolidation/output-formatters": "^3.1.12", + "consolidation/output-formatters": "^3.4", "php": ">=5.4.0", "psr/log": "^1", "symfony/console": "^2.8|^3|^4", @@ -4825,13 +4937,57 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^6", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, "branch-alias": { "dev-master": "2.x-dev" } @@ -4852,7 +5008,7 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-09-19T17:47:18+00:00" + "time": "2018-12-14T01:52:35+00:00" }, { "name": "consolidation/config", @@ -5015,25 +5171,24 @@ }, { "name": "consolidation/robo", - "version": "1.3.1", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d" + "reference": "01789e1c4f3b0d7e5b4ee0aca1843a9438ff4983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/01789e1c4f3b0d7e5b4ee0aca1843a9438ff4983", + "reference": "01789e1c4f3b0d7e5b4ee0aca1843a9438ff4983", "shasum": "" }, "require": { - "consolidation/annotated-command": "^2.8.2", + "consolidation/annotated-command": "^2.10.1", "consolidation/config": "^1.0.10", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", "consolidation/self-update": "^1", - "g1a/composer-test-scenarios": "^2", "grasmash/yaml-expander": "^1.3", "league/container": "^2.2", "php": ">=5.5.0", @@ -5050,14 +5205,15 @@ "codeception/aspect-mock": "^1|^2.1.1", "codeception/base": "^2.3.7", "codeception/verify": "^0.3.2", + "g1a/composer-test-scenarios": "^3", "goaop/framework": "~2.1.2", "goaop/parser-reflection": "^1.1.0", "natxet/cssmin": "3.0.4", "nikic/php-parser": "^3.1.5", "patchwork/jsqueeze": "~2", "pear/archive_tar": "^1.4.2", + "php-coveralls/php-coveralls": "^1", "phpunit/php-code-coverage": "~2|~4", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.8" }, "suggest": { @@ -5071,9 +5227,36 @@ ], "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "remove": [ + "goaop/framework" + ], + "config": { + "platform": { + "php": "5.5.9" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, "branch-alias": { - "dev-master": "1.x-dev", - "dev-state": "1.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -5092,7 +5275,7 @@ } ], "description": "Modern task runner", - "time": "2018-08-17T18:44:18+00:00" + "time": "2018-12-14T02:54:30+00:00" }, { "name": "consolidation/self-update", @@ -5734,39 +5917,6 @@ ], "time": "2018-07-12T10:23:15+00:00" }, - { - "name": "g1a/composer-test-scenarios", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/g1a/composer-test-scenarios.git", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880", - "shasum": "" - }, - "bin": [ - "scripts/create-scenario", - "scripts/dependency-licenses", - "scripts/install-scenario" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" - } - ], - "description": "Useful scripts for testing multiple sets of Composer dependencies.", - "time": "2018-08-08T23:37:23+00:00" - }, { "name": "grasmash/expander", "version": "1.0.0", @@ -5980,32 +6130,33 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "9f83dded91781a01c63574e387eaa769be769115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -6035,13 +6186,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2018-12-04T20:46:45+00:00" }, { "name": "jms/metadata", @@ -6351,20 +6503,20 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "2.3.11", + "version": "2.3.12", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "3ca1bd74228a61bd05520bed1ef88b5a19764d92" + "reference": "599004be3e14ebbe6fac77de2edbab934d70f19c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/3ca1bd74228a61bd05520bed1ef88b5a19764d92", - "reference": "3ca1bd74228a61bd05520bed1ef88b5a19764d92", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/599004be3e14ebbe6fac77de2edbab934d70f19c", + "reference": "599004be3e14ebbe6fac77de2edbab934d70f19c", "shasum": "" }, "require": { - "allure-framework/allure-codeception": "~1.2.6", + "allure-framework/allure-codeception": "~1.3.0", "codeception/codeception": "~2.3.4", "consolidation/robo": "^1.0.0", "epfremme/swagger-php": "^2.0", @@ -6418,7 +6570,53 @@ "magento", "testing" ], - "time": "2018-11-13T18:22:25+00:00" + "time": "2018-12-19T17:04:11+00:00" + }, + { + "name": "mikey179/vfsStream", + "version": "v1.6.5", + "source": { + "type": "git", + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", + "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "time": "2017-08-01T08:02:14+00:00" }, { "name": "moontoast/math", @@ -7527,6 +7725,46 @@ ], "time": "2018-08-09T05:50:03+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -8228,16 +8466,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "c55fe9257003b2d95c0211b3f6941e8dfd26dffd" + "reference": "db7e59fec9c82d45e745eb500e6ede2d96f4a6e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c55fe9257003b2d95c0211b3f6941e8dfd26dffd", - "reference": "c55fe9257003b2d95c0211b3f6941e8dfd26dffd", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/db7e59fec9c82d45e745eb500e6ede2d96f4a6e9", + "reference": "db7e59fec9c82d45e745eb500e6ede2d96f4a6e9", "shasum": "" }, "require": { @@ -8254,7 +8492,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8281,20 +8519,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:10:45+00:00" + "time": "2018-11-26T11:49:31+00:00" }, { "name": "symfony/config", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", - "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "url": "https://api.github.com/repos/symfony/config/zipball/005d9a083d03f588677d15391a716b1ac9b887c0", + "reference": "005d9a083d03f588677d15391a716b1ac9b887c0", "shasum": "" }, "require": { @@ -8317,7 +8555,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8344,37 +8582,48 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-09-08T13:24:10+00:00" + "time": "2018-11-30T22:21:14+00:00" }, { - "name": "symfony/css-selector", - "version": "v4.1.6", + "name": "symfony/contracts", + "version": "v1.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a" + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/d67de79a70a27d93c92c47f37ece958bf8de4d8a", - "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", "shasum": "" }, "require": { "php": "^7.1.3" }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "1.0-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\CssSelector\\": "" + "Symfony\\Contracts\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "**/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8383,51 +8632,57 @@ ], "authors": [ { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony CssSelector Component", + "description": "A set of abstractions extracted out of the Symfony components", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30" + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f6b9d893ad28aefd8942dc0469c8397e2216fe30", - "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4adc57a48d3fa7f394edfffa9e954086d7740e5", + "reference": "e4adc57a48d3fa7f394edfffa9e954086d7740e5", "shasum": "" }, "require": { "php": "^7.1.3", - "psr/container": "^1.0" + "psr/container": "^1.0", + "symfony/contracts": "^1.0" }, "conflict": { - "symfony/config": "<4.1.1", + "symfony/config": "<4.2", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-contracts-implementation": "1.0" }, "require-dev": { - "symfony/config": "~4.1", + "symfony/config": "~4.2", "symfony/expression-language": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -8441,7 +8696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8468,20 +8723,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-12-02T15:59:36+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "80e60271bb288de2a2259662cff125cff4f93f95" + "reference": "7438a32108fdd555295f443605d6de2cce473159" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/80e60271bb288de2a2259662cff125cff4f93f95", - "reference": "80e60271bb288de2a2259662cff125cff4f93f95", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7438a32108fdd555295f443605d6de2cce473159", + "reference": "7438a32108fdd555295f443605d6de2cce473159", "shasum": "" }, "require": { @@ -8498,7 +8753,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8525,20 +8780,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d528136617ff24f530e70df9605acc1b788b08d4" + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d528136617ff24f530e70df9605acc1b788b08d4", - "reference": "d528136617ff24f530e70df9605acc1b788b08d4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", + "reference": "1b31f3017fadd8cb05cf2c8aebdbf3b12a943851", "shasum": "" }, "require": { @@ -8552,7 +8807,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8579,20 +8834,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:48:45+00:00" + "time": "2018-11-26T10:55:26+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff" + "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40f0e40d37c1c8a762334618dea597d64bbb75ff", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", + "reference": "a9c38e8a3da2c03b3e71fdffa6efb0bda51390ba", "shasum": "" }, "require": { @@ -8601,7 +8856,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8633,20 +8888,20 @@ "configuration", "options" ], - "time": "2018-09-18T12:45:12+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", "shasum": "" }, "require": { @@ -8692,20 +8947,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T06:26:08+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" + "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", - "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", "shasum": "" }, "require": { @@ -8747,29 +9002,30 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.6", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" + "reference": "ec076716412274e51f8a7ea675d9515e5c311123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/ec076716412274e51f8a7ea675d9515e5c311123", + "reference": "ec076716412274e51f8a7ea675d9515e5c311123", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/contracts": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -8796,20 +9052,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.17", + "version": "v3.4.20", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f" + "reference": "291e13d808bec481eab83f301f7bff3e699ef603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/640b6c27fed4066d64b64d5903a86043f4a4de7f", - "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f", + "url": "https://api.github.com/repos/symfony/yaml/zipball/291e13d808bec481eab83f301f7bff3e699ef603", + "reference": "291e13d808bec481eab83f301f7bff3e699ef603", "shasum": "" }, "require": { @@ -8855,7 +9111,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:33:53+00:00" + "time": "2018-11-11T19:48:54+00:00" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/acceptance/tests/_data/catalog_products.csv b/dev/tests/acceptance/tests/_data/catalog_products.csv new file mode 100644 index 0000000000000..3b580172d6a05 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/catalog_products.csv @@ -0,0 +1,4 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus +"Simple Product for Test",,Default,simple,,base,"Simple Product for Test",,,,1,"Taxable Goods","Catalog, Search",123.0000,,,,simple-product-for-test,,,,,,,,,,,,"12/18/18, 7:50 AM","12/18/18, 7:50 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, +"Virtual Product for Test",,Default,virtual,,base,"Virtual Product for Test",,,0.0000,1,"Taxable Goods","Catalog, Search",99.9900,,,,virtual-product-for-test,,,,,,,,,,,,"12/18/18, 7:51 AM","12/18/18, 7:51 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, +"Api Downloadable Product for Test",,Default,downloadable,,base,"Api Downloadable Product for Test","API Product Description5c18fb47982621","API Product Short Description5c18fb47982e21",,1,"Taxable Goods","Catalog, Search",123.0000,,,,api-downloadable-product-for-test,,,,,,,,,,,,"12/18/18, 7:51 AM","12/18/18, 7:51 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, diff --git a/dev/tests/api-functional/phpunit_graphql.xml.dist b/dev/tests/api-functional/phpunit_graphql.xml.dist index 4a57c338ca3a2..aa1899d88f48e 100644 --- a/dev/tests/api-functional/phpunit_graphql.xml.dist +++ b/dev/tests/api-functional/phpunit_graphql.xml.dist @@ -52,5 +52,58 @@ <!-- Test listeners --> <listeners> <listener class="Magento\TestFramework\Event\PhpUnit"/> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output folder --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppArea"> + <string>magentoAppArea</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoCache"> + <string>magentoCache</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="magentoDataFixture"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDataFixtureBeforeTransaction"> + <string>magentoDataFixtureBeforeTransaction</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + <element key="magentoIndexerDimensionMode"> + <string>magentoIndexerDimensionMode</string> + </element> + <element key="magentoApiDataFixture"> + <string>magentoApiDataFixture</string> + </element> + <element key="Override"> + <string>Override</string> + </element> + </array> + </arguments> + </listener> </listeners> </phpunit> diff --git a/dev/tests/api-functional/phpunit_rest.xml.dist b/dev/tests/api-functional/phpunit_rest.xml.dist index a2bc077328e26..c5173e5dd432e 100644 --- a/dev/tests/api-functional/phpunit_rest.xml.dist +++ b/dev/tests/api-functional/phpunit_rest.xml.dist @@ -58,5 +58,58 @@ <!-- Test listeners --> <listeners> <listener class="Magento\TestFramework\Event\PhpUnit"/> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output folder --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppArea"> + <string>magentoAppArea</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoCache"> + <string>magentoCache</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="magentoDataFixture"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDataFixtureBeforeTransaction"> + <string>magentoDataFixtureBeforeTransaction</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + <element key="magentoIndexerDimensionMode"> + <string>magentoIndexerDimensionMode</string> + </element> + <element key="magentoApiDataFixture"> + <string>magentoApiDataFixture</string> + </element> + <element key="Override"> + <string>Override</string> + </element> + </array> + </arguments> + </listener> </listeners> </phpunit> diff --git a/dev/tests/api-functional/phpunit_soap.xml.dist b/dev/tests/api-functional/phpunit_soap.xml.dist index cc0d555538351..935f5113b67a7 100644 --- a/dev/tests/api-functional/phpunit_soap.xml.dist +++ b/dev/tests/api-functional/phpunit_soap.xml.dist @@ -57,5 +57,58 @@ <!-- Test listeners --> <listeners> <listener class="Magento\TestFramework\Event\PhpUnit"/> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output folder --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppArea"> + <string>magentoAppArea</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoCache"> + <string>magentoCache</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="magentoDataFixture"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDataFixtureBeforeTransaction"> + <string>magentoDataFixtureBeforeTransaction</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + <element key="magentoIndexerDimensionMode"> + <string>magentoIndexerDimensionMode</string> + </element> + <element key="magentoApiDataFixture"> + <string>magentoApiDataFixture</string> + </element> + <element key="Override"> + <string>Override</string> + </element> + </array> + </arguments> + </listener> </listeners> </phpunit> diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index 9562d6618d8db..d5c035733942e 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -49,7 +49,7 @@ public function testGetList($customerGroupId, $count, $value, $qty) public function getListDataProvider() { return [ - [0, 2, 5, 3], + [0, 3, 5, 3], [1, 0, null, null], ['all', 2, 8, 2], ]; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php similarity index 98% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php index f245181815217..f4e96e49a58e6 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php @@ -14,7 +14,7 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -class CustomerChangePasswordTest extends GraphQlAbstract +class ChangeCustomerPasswordTest extends GraphQlAbstract { /** * @var AccountManagementInterface diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php new file mode 100644 index 0000000000000..602d969924fbd --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php @@ -0,0 +1,247 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Integration\Api\CustomerTokenServiceInterface; + +class CreateCustomerAddressTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->addressRepository = Bootstrap::getObjectManager()->get(AddressRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer_without_addresses.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateCustomerAddress() + { + $customerId = 1; + $newAddress = [ + 'region' => [ + 'region' => 'Arizona', + 'region_id' => 4, + 'region_code' => 'AZ' + ], + 'country_id' => 'US', + 'street' => ['Line 1 Street', 'Line 2'], + 'company' => 'Company name', + 'telephone' => '123456789', + 'fax' => '123123123', + 'postcode' => '7777', + 'city' => 'City Name', + 'firstname' => 'Adam', + 'lastname' => 'Phillis', + 'middlename' => 'A', + 'prefix' => 'Mr.', + 'suffix' => 'Jr.', + 'vat_id' => '1', + 'default_shipping' => true, + 'default_billing' => false + ]; + + $mutation + = <<<MUTATION +mutation { + createCustomerAddress(input: { + region: { + region: "{$newAddress['region']['region']}" + region_id: {$newAddress['region']['region_id']} + region_code: "{$newAddress['region']['region_code']}" + } + country_id: {$newAddress['country_id']} + street: ["{$newAddress['street'][0]}","{$newAddress['street'][1]}"] + company: "{$newAddress['company']}" + telephone: "{$newAddress['telephone']}" + fax: "{$newAddress['fax']}" + postcode: "{$newAddress['postcode']}" + city: "{$newAddress['city']}" + firstname: "{$newAddress['firstname']}" + lastname: "{$newAddress['lastname']}" + middlename: "{$newAddress['middlename']}" + prefix: "{$newAddress['prefix']}" + suffix: "{$newAddress['suffix']}" + vat_id: "{$newAddress['vat_id']}" + default_shipping: true + default_billing: false + }) { + id + customer_id + region { + region + region_id + region_code + } + country_id + street + company + telephone + fax + postcode + city + firstname + lastname + middlename + prefix + suffix + vat_id + default_shipping + default_billing + } +} +MUTATION; + + $userName = 'customer@example.com'; + $password = 'password'; + + $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->assertArrayHasKey('createCustomerAddress', $response); + $this->assertArrayHasKey('customer_id', $response['createCustomerAddress']); + $this->assertEquals($customerId, $response['createCustomerAddress']['customer_id']); + $this->assertArrayHasKey('id', $response['createCustomerAddress']); + + $address = $this->addressRepository->getById($response['createCustomerAddress']['id']); + $this->assertEquals($address->getId(), $response['createCustomerAddress']['id']); + $this->assertCustomerAddressesFields($address, $response['createCustomerAddress']); + $this->assertCustomerAddressesFields($address, $newAddress); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testCreateCustomerAddressIfUserIsNotAuthorized() + { + $mutation + = <<<MUTATION +mutation{ + createCustomerAddress(input: { + prefix: "Mr." + firstname: "John" + middlename: "A" + lastname: "Smith" + telephone: "123456789" + street: ["Line 1", "Line 2"] + city: "Test City" + region: { + region_id: 1 + } + country_id: US + postcode: "9999" + default_shipping: true + default_billing: false + }) { + id + } +} +MUTATION; + $this->graphQlQuery($mutation); + } + + /** + * Verify customers with valid credentials create new address + * with missing required Firstname attribute + * + * @magentoApiDataFixture Magento/Customer/_files/customer_without_addresses.php + * @expectedException \Exception + * @expectedExceptionMessage Required parameters are missing: firstname + */ + public function testCreateCustomerAddressWithMissingAttribute() + { + $mutation + = <<<MUTATION +mutation { + createCustomerAddress(input: { + region: { + region_id: 1 + } + country_id: US + street: ["Line 1 Street","Line 2"] + company: "Company name" + telephone: "123456789" + fax: "123123123" + postcode: "7777" + city: "City Name" + firstname: "" + lastname: "Phillis" + }) { + id + } +} +MUTATION; + + $userName = 'customer@example.com'; + $password = 'password'; + $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * Verify the fields for Customer address + * + * @param AddressInterface $address + * @param array $actualResponse + */ + private function assertCustomerAddressesFields(AddressInterface $address, array $actualResponse): void + { + /** @var $addresses */ + $assertionMap = [ + ['response_field' => 'country_id', 'expected_value' => $address->getCountryId()], + ['response_field' => 'street', 'expected_value' => $address->getStreet()], + ['response_field' => 'company', 'expected_value' => $address->getCompany()], + ['response_field' => 'telephone', 'expected_value' => $address->getTelephone()], + ['response_field' => 'fax', 'expected_value' => $address->getFax()], + ['response_field' => 'postcode', 'expected_value' => $address->getPostcode()], + ['response_field' => 'city', 'expected_value' => $address->getCity()], + ['response_field' => 'firstname', 'expected_value' => $address->getFirstname()], + ['response_field' => 'lastname', 'expected_value' => $address->getLastname()], + ['response_field' => 'middlename', 'expected_value' => $address->getMiddlename()], + ['response_field' => 'prefix', 'expected_value' => $address->getPrefix()], + ['response_field' => 'suffix', 'expected_value' => $address->getSuffix()], + ['response_field' => 'vat_id', 'expected_value' => $address->getVatId()], + ['response_field' => 'default_shipping', 'expected_value' => (bool)$address->isDefaultShipping()], + ['response_field' => 'default_billing', 'expected_value' => (bool)$address->isDefaultBilling()], + ]; + $this->assertResponseFields($actualResponse, $assertionMap); + $this->assertTrue(is_array([$actualResponse['region']]), "region field must be of an array type."); + $assertionRegionMap = [ + ['response_field' => 'region', 'expected_value' => $address->getRegion()->getRegion()], + ['response_field' => 'region_code', 'expected_value' => $address->getRegion()->getRegionCode()], + ['response_field' => 'region_id', 'expected_value' => $address->getRegion()->getRegionId()] + ]; + $this->assertResponseFields($actualResponse['region'], $assertionRegionMap); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php new file mode 100644 index 0000000000000..ba0232020298f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Integration\Api\CustomerTokenServiceInterface; + +class DeleteCustomerAddressTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + $this->addressRepository = Bootstrap::getObjectManager()->get(AddressRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + */ + public function testDeleteCustomerAddress() + { + $userName = 'customer@example.com'; + $password = 'password'; + $addressId = 2; + + $mutation + = <<<MUTATION +mutation { + deleteCustomerAddress(id: {$addressId}) +} +MUTATION; + $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->assertArrayHasKey('deleteCustomerAddress', $response); + $this->assertEquals(true, $response['deleteCustomerAddress']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testDeleteCustomerAddressIfUserIsNotAuthorized() + { + $addressId = 1; + $mutation + = <<<MUTATION +mutation { + deleteCustomerAddress(id: {$addressId}) +} +MUTATION; + $this->graphQlQuery($mutation); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * + * @expectedException \Exception + * @expectedExceptionMessage Customer Address 2 is set as default shipping address and can not be deleted + */ + public function testDeleteDefaultShippingCustomerAddress() + { + $userName = 'customer@example.com'; + $password = 'password'; + $addressId = 2; + + $address = $this->addressRepository->getById($addressId); + $address->setIsDefaultShipping(true); + $this->addressRepository->save($address); + + $mutation + = <<<MUTATION +mutation { + deleteCustomerAddress(id: {$addressId}) +} +MUTATION; + $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php + * + * @expectedException \Exception + * @expectedExceptionMessage Customer Address 2 is set as default billing address and can not be deleted + */ + public function testDeleteDefaultBillingCustomerAddress() + { + $userName = 'customer@example.com'; + $password = 'password'; + $addressId = 2; + + $address = $this->addressRepository->getById($addressId); + $address->setIsDefaultBilling(true); + $this->addressRepository->save($address); + + $mutation + = <<<MUTATION +mutation { + deleteCustomerAddress(id: {$addressId}) +} +MUTATION; + $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Address id 9999 does not exist. + */ + public function testDeleteNonExistCustomerAddress() + { + $userName = 'customer@example.com'; + $password = 'password'; + $mutation + = <<<MUTATION +mutation { + deleteCustomerAddress(id: 9999) +} +MUTATION; + $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetAddressesTest.php similarity index 66% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetAddressesTest.php index 9b7e3f28327da..53eb80335ff21 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetAddressesTest.php @@ -14,7 +14,7 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; -class AddressesTest extends GraphQlAbstract +class GetAddressesTest extends GraphQlAbstract { /** * @magentoApiDataFixture Magento/Customer/_files/customer.php @@ -25,20 +25,9 @@ public function testGetCustomerWithAddresses() $query = <<<QUERY { - customer - { - created_at - group_id - prefix - firstname - middlename - lastname - suffix - email - default_billing - default_shipping + customer { id - addresses{ + addresses { id customer_id region_id @@ -71,37 +60,10 @@ public function testGetCustomerWithAddresses() is_array([$response['customer']['addresses']]), " Addresses field must be of an array type." ); - $this->assertCustomerFields($customer, $response['customer']); + self::assertEquals($customer->getId(), $response['customer']['id']); $this->assertCustomerAddressesFields($customer, $response); } - /** - * Verify the all the whitelisted fields for a Customer Object - * - * @param CustomerInterface $customer - * @param $actualResponse - */ - public function assertCustomerFields($customer, $actualResponse) - { - // ['customer_object_field_name', 'expected_value'] - $assertionMap = [ - ['response_field' => 'id', 'expected_value' => $customer->getId()], - ['response_field' => 'created_at', 'expected_value' => $customer->getCreatedAt()], - ['response_field' => 'group_id', 'expected_value' => $customer->getGroupId()], - ['response_field' => 'prefix', 'expected_value' => $customer->getPrefix()], - ['response_field' => 'firstname', 'expected_value' => $customer->getFirstname()], - ['response_field' => 'middlename', 'expected_value' => $customer->getMiddlename()], - ['response_field' => 'lastname', 'expected_value' => $customer->getLastname()], - ['response_field' => 'suffix', 'expected_value' => $customer->getSuffix()], - ['response_field' => 'email', 'expected_value' => $customer->getEmail()], - ['response_field' => 'default_shipping', 'expected_value' => (bool)$customer->getDefaultShipping()], - ['response_field' => 'default_billing', 'expected_value' => (bool)$customer->getDefaultBilling()], - ['response_field' => 'id', 'expected_value' => $customer->getId()] - ]; - - $this->assertResponseFields($actualResponse, $assertionMap); - } - /** * Verify the fields for CustomerAddress object * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetCustomerTest.php new file mode 100644 index 0000000000000..928a263e8531b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GetCustomerTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Model\CustomerAuthUpdate; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class GetCustomerTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var CustomerAuthUpdate + */ + private $customerAuthUpdate; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + $this->customerAuthUpdate = Bootstrap::getObjectManager()->get(CustomerAuthUpdate::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetCustomer() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + + $this->assertEquals('John', $response['customer']['firstname']); + $this->assertEquals('Smith', $response['customer']['lastname']); + $this->assertEquals($currentEmail, $response['customer']['email']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testGetCustomerIfUserIsNotAuthorized() + { + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The account is locked. + */ + public function testGetCustomerIfAccountIsLocked() + { + $this->lockCustomer(1); + + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * @param int $customerId + * @return void + */ + private function lockCustomer(int $customerId): void + { + $customerSecure = $this->customerRegistry->retrieveSecureData($customerId); + $customerSecure->setLockExpires('2030-12-31 00:00:00'); + $this->customerAuthUpdate->saveAuth($customerId); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php new file mode 100644 index 0000000000000..519fe2b1405a0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php @@ -0,0 +1,240 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Integration\Api\CustomerTokenServiceInterface; + +class UpdateCustomerAddressTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + $this->addressRepository = Bootstrap::getObjectManager()->get(AddressRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Customer/_files/customer_address.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testUpdateCustomerAddress() + { + $userName = 'customer@example.com'; + $password = 'password'; + $customerId = 1; + $addressId = 1; + + $updateAddress = [ + 'region' => [ + 'region' => 'Alaska', + 'region_id' => 2, + 'region_code' => 'AK' + ], + 'country_id' => 'US', + 'street' => ['Line 1 Street', 'Line 2'], + 'company' => 'Company Name', + 'telephone' => '123456789', + 'fax' => '123123123', + 'postcode' => '7777', + 'city' => 'City Name', + 'firstname' => 'Adam', + 'lastname' => 'Phillis', + 'middlename' => 'A', + 'prefix' => 'Mr.', + 'suffix' => 'Jr.', + 'vat_id' => '1', + 'default_shipping' => true, + 'default_billing' => true + ]; + $defaultShippingText = $updateAddress['default_shipping'] ? "true": "false"; + $defaultBillingText = $updateAddress['default_billing'] ? "true": "false"; + + $mutation + = <<<MUTATION +mutation { + updateCustomerAddress(id: {$addressId}, input: { + region: { + region: "{$updateAddress['region']['region']}" + region_id: {$updateAddress['region']['region_id']} + region_code: "{$updateAddress['region']['region_code']}" + } + country_id: {$updateAddress['country_id']} + street: ["{$updateAddress['street'][0]}","{$updateAddress['street'][1]}"] + company: "{$updateAddress['company']}" + telephone: "{$updateAddress['telephone']}" + fax: "{$updateAddress['fax']}" + postcode: "{$updateAddress['postcode']}" + city: "{$updateAddress['city']}" + firstname: "{$updateAddress['firstname']}" + lastname: "{$updateAddress['lastname']}" + middlename: "{$updateAddress['middlename']}" + prefix: "{$updateAddress['prefix']}" + suffix: "{$updateAddress['suffix']}" + vat_id: "{$updateAddress['vat_id']}" + default_shipping: {$defaultShippingText} + default_billing: {$defaultBillingText} + }) { + id + customer_id + region { + region + region_id + region_code + } + country_id + street + company + telephone + fax + postcode + city + firstname + lastname + middlename + prefix + suffix + vat_id + default_shipping + default_billing + } +} +MUTATION; + + $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->assertArrayHasKey('updateCustomerAddress', $response); + $this->assertArrayHasKey('customer_id', $response['updateCustomerAddress']); + $this->assertEquals($customerId, $response['updateCustomerAddress']['customer_id']); + $this->assertArrayHasKey('id', $response['updateCustomerAddress']); + + $address = $this->addressRepository->getById($addressId); + $this->assertEquals($address->getId(), $response['updateCustomerAddress']['id']); + $this->assertCustomerAddressesFields($address, $response['updateCustomerAddress']); + $this->assertCustomerAddressesFields($address, $updateAddress); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testUpdateCustomerAddressIfUserIsNotAuthorized() + { + $addressId = 1; + $mutation + = <<<MUTATION +mutation { + updateCustomerAddress(id:{$addressId}, input: { + city: "New City" + postcode: "5555" + }) { + id + } +} +MUTATION; + $this->graphQlQuery($mutation); + } + + /** + * Verify customers with credentials update address + * with missing required Firstname attribute + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Customer/_files/customer_address.php + * @expectedException \Exception + * @expectedExceptionMessage Required parameters are missing: firstname + */ + public function testUpdateCustomerAddressWithMissingAttribute() + { + $userName = 'customer@example.com'; + $password = 'password'; + $addressId = 1; + + $mutation + = <<<MUTATION +mutation { + updateCustomerAddress(id: {$addressId}, input: { + firstname: "" + lastname: "Phillis" + }) { + id + } +} +MUTATION; + $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * Verify the fields for Customer address + * + * @param AddressInterface $address + * @param array $actualResponse + */ + private function assertCustomerAddressesFields(AddressInterface $address, $actualResponse): void + { + /** @var $addresses */ + $assertionMap = [ + ['response_field' => 'country_id', 'expected_value' => $address->getCountryId()], + ['response_field' => 'street', 'expected_value' => $address->getStreet()], + ['response_field' => 'company', 'expected_value' => $address->getCompany()], + ['response_field' => 'telephone', 'expected_value' => $address->getTelephone()], + ['response_field' => 'fax', 'expected_value' => $address->getFax()], + ['response_field' => 'postcode', 'expected_value' => $address->getPostcode()], + ['response_field' => 'city', 'expected_value' => $address->getCity()], + ['response_field' => 'firstname', 'expected_value' => $address->getFirstname()], + ['response_field' => 'lastname', 'expected_value' => $address->getLastname()], + ['response_field' => 'middlename', 'expected_value' => $address->getMiddlename()], + ['response_field' => 'prefix', 'expected_value' => $address->getPrefix()], + ['response_field' => 'suffix', 'expected_value' => $address->getSuffix()], + ['response_field' => 'vat_id', 'expected_value' => $address->getVatId()], + ['response_field' => 'default_shipping', 'expected_value' => (bool)$address->isDefaultShipping()], + ['response_field' => 'default_billing', 'expected_value' => (bool)$address->isDefaultBilling()], + ]; + $this->assertResponseFields($actualResponse, $assertionMap); + $this->assertTrue(is_array([$actualResponse['region']]), "region field must be of an array type."); + // https://github.com/magento/graphql-ce/issues/270 +// $assertionRegionMap = [ +// ['response_field' => 'region', 'expected_value' => $address->getRegion()->getRegion()], +// ['response_field' => 'region_code', 'expected_value' => $address->getRegion()->getRegionCode()], +// ['response_field' => 'region_id', 'expected_value' => $address->getRegion()->getRegionId()] +// ]; +// $this->assertResponseFields($actualResponse['region'], $assertionRegionMap); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php similarity index 73% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php index 942e321a78718..c11c1385f7412 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php @@ -7,26 +7,19 @@ namespace Magento\GraphQl\Customer; -use Magento\Customer\Api\AccountManagementInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\CustomerAuthUpdate; use Magento\Customer\Model\CustomerRegistry; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -class AccountInformationTest extends GraphQlAbstract +class UpdateCustomerTest extends GraphQlAbstract { /** * @var CustomerTokenServiceInterface */ private $customerTokenService; - /** - * @var AccountManagementInterface - */ - private $accountManagement; - /** * @var CustomerRegistry */ @@ -37,92 +30,19 @@ class AccountInformationTest extends GraphQlAbstract */ private $customerAuthUpdate; - /** - * @var CustomerRepositoryInterface - */ - private $customerRepository; - protected function setUp() { parent::setUp(); $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); - $this->accountManagement = Bootstrap::getObjectManager()->get(AccountManagementInterface::class); $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); $this->customerAuthUpdate = Bootstrap::getObjectManager()->get(CustomerAuthUpdate::class); - $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - */ - public function testGetAccountInformation() - { - $currentEmail = 'customer@example.com'; - $currentPassword = 'password'; - - $query = <<<QUERY -query { - customer { - firstname - lastname - email - } -} -QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); - - $this->assertEquals('John', $response['customer']['firstname']); - $this->assertEquals('Smith', $response['customer']['lastname']); - $this->assertEquals($currentEmail, $response['customer']['email']); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage The current customer isn't authorized. - */ - public function testGetAccountInformationIfUserIsNotAuthorized() - { - $query = <<<QUERY -query { - customer { - firstname - lastname - email - } -} -QUERY; - $this->graphQlQuery($query); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @expectedException \Exception - * @expectedExceptionMessage The account is locked. - */ - public function testGetAccountInformationIfCustomerIsLocked() - { - $this->lockCustomer(1); - - $currentEmail = 'customer@example.com'; - $currentPassword = 'password'; - - $query = <<<QUERY -query { - customer { - firstname - lastname - email - } -} -QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php */ - public function testUpdateAccountInformation() + public function testUpdateCustomer() { $currentEmail = 'customer@example.com'; $currentPassword = 'password'; @@ -161,7 +81,7 @@ public function testUpdateAccountInformation() * @expectedException \Exception * @expectedExceptionMessage "input" value should be specified */ - public function testUpdateAccountInformationIfInputDataIsEmpty() + public function testUpdateCustomerIfInputDataIsEmpty() { $currentEmail = 'customer@example.com'; $currentPassword = 'password'; @@ -186,7 +106,7 @@ public function testUpdateAccountInformationIfInputDataIsEmpty() * @expectedException \Exception * @expectedExceptionMessage The current customer isn't authorized. */ - public function testUpdateAccountInformationIfUserIsNotAuthorized() + public function testUpdateCustomerIfUserIsNotAuthorized() { $newFirstname = 'Richard'; @@ -211,7 +131,7 @@ public function testUpdateAccountInformationIfUserIsNotAuthorized() * @expectedException \Exception * @expectedExceptionMessage The account is locked. */ - public function testUpdateAccountInformationIfCustomerIsLocked() + public function testUpdateCustomerIfAccountIsLocked() { $this->lockCustomer(1); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php index 589b0bc7746f8..9c969befa328b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php @@ -83,9 +83,21 @@ public function testOrdersQuery() $actualData = $response['customerOrders']['items']; foreach ($expectedData as $key => $data) { - $this->assertEquals($data['increment_id'], $actualData[$key]['increment_id']); - $this->assertEquals($data['grand_total'], $actualData[$key]['grand_total']); - $this->assertEquals($data['status'], $actualData[$key]['status']); + $this->assertEquals( + $data['increment_id'], + $actualData[$key]['increment_id'], + "increment_id is different than the expected for order - " . $data['increment_id'] + ); + $this->assertEquals( + $data['grand_total'], + $actualData[$key]['grand_total'], + "grand_total is different than the expected for order - " . $data['increment_id'] + ); + $this->assertEquals( + $data['status'], + $actualData[$key]['status'], + "status is different than the expected for order - " . $data['increment_id'] + ); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/CartItemRepositoryTest.php new file mode 100644 index 0000000000000..602493481449f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/CartItemRepositoryTest.php @@ -0,0 +1,68 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\GroupedProduct\Api; + +use Magento\Catalog\Model\CustomOptions\CustomOptionProcessor; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Quote\Model\Quote; +use Magento\TestFramework\TestCase\WebapiAbstract; + +class CartItemRepositoryTest extends WebapiAbstract +{ + const SERVICE_VERSION = 'V1'; + const SERVICE_NAME = 'quoteCartItemRepositoryV1'; + const RESOURCE_PATH = '/V1/carts/'; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock.php + */ + public function testAddGroupedProductToCartThatHasAnOutOfStockItemInTheGroup() + { + $this->_markTestAsRestOnly(); + + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load('100000003'); + $productSku = $product->getSku(); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); + $quote->load('test_order_1', 'reserved_order_id'); + $cartId = $quote->getId(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/items', + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $requestData = [ + 'cartItem' => [ + 'sku' => $productSku, + 'qty' => 1, + 'quote_id' => $cartId, + ], + ]; + $this->_webApiCall($serviceInfo, $requestData); + $this->assertTrue($quote->hasProductId('100000001')); + $this->assertFalse($quote->hasProductId('100000002')); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php index 09c49bed1de83..db96728e206be 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php @@ -133,6 +133,8 @@ public function testOrderGetExtensionAttributes(): void self::assertEquals($expectedTax['type'], $appliedTaxes[0]['type']); self::assertNotEmpty($appliedTaxes[0]['applied_taxes']); self::assertEquals(true, $result['extension_attributes']['converting_from_quote']); + self::assertArrayHasKey('payment_additional_info', $result['extension_attributes']); + self::assertNotEmpty($result['extension_attributes']['payment_additional_info']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php index 3ab93f9aecb99..9ba648c73276b 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php @@ -77,4 +77,36 @@ protected function assertOrderItem(\Magento\Sales\Model\Order\Item $orderItem, a $this->assertEquals($orderItem->getBasePrice(), $response['base_price']); $this->assertEquals($orderItem->getRowTotal(), $response['row_total']); } + + /** + * @magentoApiDataFixture Magento/Sales/_files/order_with_discount.php + */ + public function testGetOrderWithDiscount() + { + /** @var \Magento\Sales\Model\Order $order */ + $order = $this->objectManager->create(\Magento\Sales\Model\Order::class); + $order->loadByIncrementId(self::ORDER_INCREMENT_ID); + /** @var \Magento\Sales\Model\Order\Item $orderItem */ + $orderItem = current($order->getItems()); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $orderItem->getId(), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'get', + ], + ]; + + $response = $this->_webApiCall($serviceInfo, ['id' => $orderItem->getId()]); + + $this->assertTrue(is_array($response)); + $this->assertEquals(8.00, $response['row_total']); + $this->assertEquals(8.00, $response['base_row_total']); + $this->assertEquals(9.00, $response['row_total_incl_tax']); + $this->assertEquals(9.00, $response['base_row_total_incl_tax']); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php index 5050b6be7e56c..506f82eab7ae2 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php @@ -90,6 +90,8 @@ public function testOrderListExtensionAttributes() $this->assertEquals($expectedTax['type'], $appliedTaxes[0]['type']); $this->assertNotEmpty($appliedTaxes[0]['applied_taxes']); $this->assertEquals(true, $result['items'][0]['extension_attributes']['converting_from_quote']); + $this->assertArrayHasKey('payment_additional_info', $result['items'][0]['extension_attributes']); + $this->assertNotEmpty($result['items'][0]['extension_attributes']['payment_additional_info']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php index 12cbe1ca0e5e2..8e5373ea76576 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Service\V1; +use Magento\Sales\Model\Order; + /** * API test for creation of Creditmemo for certain Order. */ @@ -86,10 +88,10 @@ public function testShortRequest() 'Failed asserting that proper shipping amount of the Order was refunded' ); - $this->assertNotEquals( - $existingOrder->getStatus(), + $this->assertEquals( + Order::STATE_COMPLETE, $updatedOrder->getStatus(), - 'Failed asserting that order status was changed' + 'Failed asserting that order status has not changed' ); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { $this->fail('Failed asserting that Creditmemo was created'); diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php index d1fd351302414..6dbf2b1aa6a12 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php @@ -6,9 +6,9 @@ namespace Magento\Mtf\Client\Element; -use Magento\Mtf\ObjectManager; -use Magento\Mtf\Client\Locator; use Magento\Mtf\Client\ElementInterface; +use Magento\Mtf\Client\Locator; +use Magento\Mtf\ObjectManager; /** * Typified element class for conditions. @@ -135,6 +135,13 @@ class ConditionsElement extends SimpleElement */ protected $chooserGridLocator = 'div[id*=chooser]'; + /** + * Datepicker xpath. + * + * @var string + */ + private $datepicker = './/*[contains(@class,"ui-datepicker-trigger")]'; + /** * Key of last find param. * @@ -189,10 +196,7 @@ class ConditionsElement extends SimpleElement protected $exception; /** - * Set value to conditions. - * - * @param string $value - * @return void + * @inheritdoc */ public function setValue($value) { @@ -411,7 +415,16 @@ protected function fillText($rule, ElementInterface $param) { $value = $param->find('input', Locator::SELECTOR_TAG_NAME); if ($value->isVisible()) { - $value->setValue($rule); + if (!$value->getAttribute('readonly')) { + $value->setValue($rule); + } else { + $datepicker = $param->find( + $this->datepicker, + Locator::SELECTOR_XPATH, + DatepickerElement::class + ); + $datepicker->setValue($rule); + } $apply = $param->find('.//*[@class="rule-param-apply"]', Locator::SELECTOR_XPATH); if ($apply->isVisible()) { diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php index a0e350cb3da43..eb277c2cc43dd 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php @@ -66,13 +66,16 @@ public function setValue($value) $date = $this->parseDate($value); $date[1] = ltrim($date[1], '0'); $this->click(); - $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); $datapicker = $this->find($this->datePickerBlock, Locator::SELECTOR_XPATH); + $datepickerClose = $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH); + if (!$datepickerClose->isVisible()) { + $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); + } $datapicker->find($this->datePickerYear, Locator::SELECTOR_XPATH, 'select')->setValue($date[2]); $datapicker->find($this->datePickerMonth, Locator::SELECTOR_XPATH, 'select')->setValue($date[0]); $datapicker->find(sprintf($this->datePickerCalendar, $date[1]), Locator::SELECTOR_XPATH)->click(); if ($datapicker->isVisible()) { - $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH)->click(); + $datepickerClose->click(); } } diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml index 9c19f80e91d39..cdb73c5d36f25 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate to menu chapter"> <variation name="NavigateMenuTestBIEssentials" summary="Navigate through BI Essentials admin menu to Sign Up page" ticketId="MAGETWO-63700"> + <data name="issue" xsi:type="string">MAGETWO-97261: Magento\Backend\Test\TestCase\NavigateMenuTest fails on Jenkins</data> <data name="menuItem" xsi:type="string">Reports > BI Essentials</data> <data name="waitMenuItemNotVisible" xsi:type="boolean">false</data> <data name="businessIntelligenceLink" xsi:type="string">https://dashboard.rjmetrics.com/v2/magento/signup</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php index 2ac4fb81ae604..d591f3b44462a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php @@ -403,4 +403,21 @@ public function getFileOptionElements() { return $this->_rootElement->getElements($this->hintMessage); } + + /** + * @inheritdoc + */ + protected function _fill(array $fields, SimpleElement $element = null) + { + $context = ($element === null) ? $this->_rootElement : $element; + foreach ($fields as $name => $field) { + $element = $this->getElement($context, $field); + if (!$element->isDisabled()) { + $element->getContext()->hover(); + $element->setValue($field['value']); + } else { + throw new \Exception("Unable to set value to field '$name' as it's disabled."); + } + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml index a840b81a5d206..c6a66beac7c79 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\CreateCategoryEntityTest" summary="Create Category from Category Page" ticketId="MAGETWO-23411"> <variation name="CreateCategoryEntityTestVariation1_RootCategory_RequiredFields"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create root category with required fields only</data> <data name="addCategory" xsi:type="string">addRootCategory</data> <data name="category/data/is_active" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php index 0770de7f7d7a6..f79cf8d7eb7fa 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php @@ -117,7 +117,6 @@ private function processLogin() if ($this->checkoutMethod === 'login') { if ($this->checkoutOnepage->getAuthenticationPopupBlock()->isVisible()) { $this->checkoutOnepage->getAuthenticationPopupBlock()->loginCustomer($this->customer); - sleep(5); $this->clickProceedToCheckoutStep->run(); } else { $this->checkoutOnepage->getLoginBlock()->loginCustomer($this->customer); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php index cba358549c3d7..13ef742d0627c 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php @@ -20,14 +20,14 @@ class AddressesAdditional extends Block * * @var string */ - protected $addressSelector = '//li[address[contains(.,"%s")]]'; + protected $addressSelector = '//tbody//tr[contains(.,"%s")]'; /** * Selector for addresses block * * @var string */ - protected $addressesSelector = '//li[address]'; + protected $addressesSelector = '.additional-addresses'; /** * Selector for delete link @@ -74,16 +74,19 @@ public function deleteAdditionalAddress(Address $address) */ public function isAdditionalAddressExists($address) { - $additionalAddressExists = false; - - $addresses = $this->_rootElement->getElements($this->addressesSelector, Locator::SELECTOR_XPATH); - foreach ($addresses as $addressBlock) { - if (strpos($addressBlock->getText(), $address) === 0) { - $additionalAddressExists = $addressBlock->isVisible(); + $addressExists = true; + foreach (explode("\n", $address) as $addressItem) { + $addressElement = $this->_rootElement->find( + sprintf($this->addressSelector, $addressItem), + Locator::SELECTOR_XPATH + ); + if (!$addressElement->isVisible()) { + $addressExists = false; break; } } - return $additionalAddressExists; + + return $addressExists; } /** diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php index f9a3989d8f574..8d8a0cfe5ea1a 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php @@ -58,6 +58,11 @@ protected function getPattern() . "{{lastname}}{{depend}} {{suffix}}{{/depend}}\n{{/depend}}{{street}}\n" . "{{city}}, {{{$region}}} {{postcode}}\n{{country_id}}\n{{depend}}{{telephone}}{{/depend}}"; break; + case "html_without_company_separated_names": + $outputPattern = "{{depend}}{{prefix}}\n{{/depend}}{{firstname}}\n{{depend}}{{middlename}}\n{{/depend}}" + . "{{lastname}}{{depend}}\n{{suffix}}{{/depend}}\n{{/depend}}{{street}}\n" + . "{{city}}\n{{{$region}}}\n{{postcode}}\n{{country_id}}\n{{depend}}{{telephone}}{{/depend}}"; + break; case "html_for_select_element": $outputPattern = "{{depend}}{{prefix}} {{/depend}}{{firstname}} {{depend}}{{middlename}} {{/depend}}" . "{{lastname}}{{depend}} {{suffix}}{{/depend}}, {{/depend}}{{street}}, " diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.php index 83d86669aa8e5..f456635882ed9 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/CustomerForm.php @@ -65,6 +65,7 @@ class CustomerForm extends FormTabs * @param FixtureInterface $customer * @param FixtureInterface|FixtureInterface[]|null $address * @return $this + * @throws \Exception */ public function fillCustomer(FixtureInterface $customer, $address = null) { @@ -76,12 +77,29 @@ public function fillCustomer(FixtureInterface $customer, $address = null) } if (null !== $address) { $this->openTab('addresses'); - $this->getTab('addresses')->fillAddresses($address); + $this->fillCustomerAddress($address); } return $this; } + /** + * Fill customer address by provided in parameter data + * + * @param FixtureInterface|FixtureInterface[] $address + * @return $this + * @throws \Exception + */ + public function fillCustomerAddress($address) + { + $addressesTab = $this->getTab('addresses'); + $this->openTab('addresses'); + $addressesTab->waitForAddressesGrid(); + $addressesTab->fillAddresses($address); + + return $this; + } + /** * Update Customer forms on tabs by customer, addresses data. * @@ -98,13 +116,16 @@ public function updateCustomer(FixtureInterface $customer, $address = null, Addr if ($isHasData) { parent::fill($customer); } + $addressesTab = $this->getTab('addresses'); if ($addressToDelete !== null) { $this->openTab('addresses'); - $this->getTab('addresses')->deleteCustomerAddress($addressToDelete); + $addressesTab->waitForAddressesGrid(); + $addressesTab->deleteCustomerAddress($addressToDelete); } if ($address !== null) { $this->openTab('addresses'); - $this->getTab('addresses')->updateAddresses($address); + $addressesTab->waitForAddressesGrid(); + $addressesTab->updateAddresses($address); } return $this; @@ -124,6 +145,9 @@ public function getDataCustomer(FixtureInterface $customer, $address = null) $data = ['customer' => $customer->hasData() ? parent::getData($customer) : parent::getData()]; if (null !== $address) { $this->openTab('addresses'); + $this->waitForElementNotVisible($this->tabReadiness); + $this->waitForm(); + $this->getTab('addresses')->waitForAddressesGrid(); $data['addresses'] = $this->getTab('addresses')->getDataAddresses($address); } @@ -148,8 +172,10 @@ protected function waitForm() */ public function openTab($tabName) { + $this->waitForElementNotVisible($this->tabReadiness); parent::openTab($tabName); $this->waitForElementNotVisible($this->tabReadiness); + $this->waitForm(); return $this; } @@ -161,13 +187,10 @@ public function openTab($tabName) */ public function getJsErrors() { - $tabs = ['account_information', 'addresses']; $jsErrors = []; - foreach ($tabs as $tabName) { - $tab = $this->getTab($tabName); - $this->openTab($tabName); - $jsErrors = array_merge($jsErrors, $tab->getJsErrors()); - } + $tab = $this->getTab('account_information'); + $this->openTab('account_information'); + $jsErrors = array_merge($jsErrors, $tab->getJsErrors()); return $jsErrors; } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php index 86df3ea912104..99a79a8a4a85c 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.php @@ -29,7 +29,7 @@ class Addresses extends Tab * * @var string */ - protected $addNewAddress = '.address-list-actions .add'; + protected $addNewAddress = '.add-new-address-button'; /** * Selector for address block. @@ -48,25 +48,32 @@ class Addresses extends Tab protected $deleteAddress = '.action-delete'; /** - * Accept button selector. + * Open customer address. * * @var string */ - private $confirmModal = '.confirm._show[data-role=modal]'; + protected $customerAddress = '//*[contains(@class, "address-list-item")][%d]'; /** - * Open customer address. + * Magento loader. * * @var string */ - protected $customerAddress = '//*[contains(@class, "address-list-item")][%d]'; + protected $loader = '//ancestor::body/div[@data-role="loader"]'; /** - * Magento loader. + * Customer address modal window. * * @var string */ - protected $loader = '//ancestor::body/div[@data-role="loader"]'; + private $customerAddressModalForm = '.customer_form_areas_address_address_customer_address_update_modal'; + + /** + * Customer addresses list grid. + * + * @var string + */ + private $customerAddressesGrid = '.customer_form_areas_address_address_customer_address_listing'; /** * Object Manager. @@ -107,9 +114,11 @@ public function __construct( public function fillAddresses($address) { $addresses = is_array($address) ? $address : [$address]; + $customerAddressForm = $this->getCustomerAddressModalForm(); foreach ($addresses as $address) { $this->addNewAddress(); - $this->setFieldsData($address->getData(), $this->_rootElement); + $customerAddressForm->fillAddressData($address); + $customerAddressForm->saveAddress(); } return $this; @@ -136,8 +145,9 @@ public function updateAddresses($address) if (!$this->isVisibleCustomerAddress($addressNumber)) { $this->addNewAddress(); + } else { + $this->openCustomerAddress($addressNumber); } - $this->openCustomerAddress($addressNumber); $defaultAddress = ['default_billing' => 'No', 'default_shipping' => 'No']; $addressData = $address->getData(); @@ -146,9 +156,10 @@ public function updateAddresses($address) $defaultAddress[$key] = $value; } } - $this->_fill($this->dataMapping($defaultAddress)); - - $this->setFieldsData(array_diff($addressData, $defaultAddress), $this->_rootElement); + $customerAddressForm = $this->getCustomerAddressModalForm(); + $customerAddressForm->setFieldsData($this->dataMapping($defaultAddress)); + $customerAddressForm->setFieldsData(array_diff($addressData, $defaultAddress)); + $customerAddressForm->saveAddress(); } return $this; @@ -168,6 +179,10 @@ public function getDataAddresses($address = null) foreach ($addresses as $addressNumber => $address) { $hasData = (null !== $address) && $address->hasData(); + $customerAddressesGrid = $this->getCustomerAddressesGrid(); + if ($hasData) { + $customerAddressesGrid->search($address->getData()); + } $isVisibleCustomerAddress = $this->isVisibleCustomerAddress($addressNumber); if ($hasData && !$isVisibleCustomerAddress) { @@ -177,14 +192,36 @@ public function getDataAddresses($address = null) if (!$hasData && !$isVisibleCustomerAddress) { $data[$addressNumber] = []; } else { - $this->openCustomerAddress($addressNumber); - $data[$addressNumber] = $this->getData($address, $this->_rootElement); + $customerAddressesGrid->openFirstRow(); + $data[$addressNumber] = $this->getCustomerAddressModalForm() + ->getData($address, $this->browser->find($this->customerAddressModalForm)); + $this->getCustomerAddressModalForm()->clickCancelButton(); } } return $data; } + /** + * Get data from Customer addresses. + * + * @param FixtureInterface|FixtureInterface[]|null $address + * @return array|null + * @throws \Exception + */ + public function getAddressFromFirstRow($address = null) + { + $customerAddressesGrid = $this->getCustomerAddressesGrid(); + $customerAddressesGrid->resetFilter(); + $customerAddressesGrid->openFirstRow(); + if ($this->getCustomerAddressModalForm()->isVisible()) { + $address = $this->getCustomerAddressModalForm() + ->getData($address, $this->browser->find($this->customerAddressModalForm)); + } + + return $address; + } + /** * Get data to fields on tab. * @@ -206,6 +243,7 @@ public function getFieldsData($fields = null, SimpleElement $element = null) protected function addNewAddress() { $this->_rootElement->find($this->addNewAddress)->click(); + $this->waitForElementVisible($this->customerAddressModalForm); } /** @@ -216,31 +254,24 @@ protected function addNewAddress() */ protected function openCustomerAddress($addressNumber) { - $addressTab = $this->_rootElement->find( - sprintf($this->customerAddress, $addressNumber), - Locator::SELECTOR_XPATH - ); - - if (!$addressTab->isVisible()) { + $customerAddressesGrid = $this->getCustomerAddressesGrid(); + if (!$customerAddressesGrid->getFirstRow()->isVisible()) { throw new \Exception("Can't open customer address #{$addressNumber}"); } - $addressTab->click(); + $customerAddressesGrid->openFirstRow(); } /** * Check is visible customer address. * - * @param int $addressNumber * @return bool */ - protected function isVisibleCustomerAddress($addressNumber) + protected function isVisibleCustomerAddress() { - $addressTab = $this->_rootElement->find( - sprintf($this->customerAddress, $addressNumber), - Locator::SELECTOR_XPATH - ); + $customerAddressesGrid = $this->getCustomerAddressesGrid(); + $customerAddressesGrid->isFirstRowVisible(); - return $addressTab->isVisible(); + return $customerAddressesGrid->isFirstRowVisible(); } /** @@ -279,24 +310,43 @@ public function getCountriesList($addressNumber) */ public function deleteCustomerAddress(Address $addressToDelete) { - $addressRenderer = $this->objectManager->create( - \Magento\Customer\Test\Block\Address\Renderer::class, - ['address' => $addressToDelete, 'type' => 'html'] - ); - $addressToDelete = $addressRenderer->render(); + $customerAddressesGrid = $this->getCustomerAddressesGrid(); + $customerAddressesGrid->deleteCustomerAddress($addressToDelete->getData()); - $dataList = explode("\n", $addressToDelete); - $dataList = implode("') and contains(.,'", $dataList); + return $this; + } - $this->_rootElement - ->find(sprintf($this->addressSelector, $dataList), Locator::SELECTOR_XPATH) - ->find($this->deleteAddress)->click(); + /** + * Get new/update customer address modal form. + * + * @return \Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses\AddressForm + */ + public function getCustomerAddressModalForm() + { + return $this->blockFactory->create( + \Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses\AddressForm::class, + ['element' => $this->browser->find($this->customerAddressModalForm)] + ); + } - $element = $this->browser->find($this->confirmModal); - /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ - $modal = $this->blockFactory->create(\Magento\Ui\Test\Block\Adminhtml\Modal::class, ['element' => $element]); - $modal->acceptAlert(); + /** + * Get customer addresses grid. + * + * @return \Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses\AddressesGrid + */ + public function getCustomerAddressesGrid() + { + return $this->blockFactory->create( + \Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses\AddressesGrid::class, + ['element' => $this->browser->find($this->customerAddressesGrid)] + ); + } - return $this; + /** + * Wait for addresses grid rendering + */ + public function waitForAddressesGrid() + { + $this->waitForElementVisible($this->customerAddressesGrid); } } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.xml deleted file mode 100644 index 8e16226adcc3f..0000000000000 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses.xml +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<mapping strict="0"> - <fields> - <prefix> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[prefix]"]</selector> - <strategy>css selector</strategy> - </prefix> - <firstname> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[firstname]"]</selector> - <strategy>css selector</strategy> - </firstname> - <middlename> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[middlename]"]</selector> - <strategy>css selector</strategy> - </middlename> - <lastname> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[lastname]"]</selector> - <strategy>css selector</strategy> - </lastname> - <suffix> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[suffix]"]</selector> - <strategy>css selector</strategy> - </suffix> - <company> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[company]"]</selector> - <strategy>css selector</strategy> - </company> - <street> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[street][0]"]</selector> - <strategy>css selector</strategy> - </street> - <city> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[city]"]</selector> - <strategy>css selector</strategy> - </city> - <country_id> - <input>select</input> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[country_id]"]</selector> - <strategy>css selector</strategy> - </country_id> - <region_id> - <input>select</input> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[region_id]"]</selector> - <strategy>css selector</strategy> - </region_id> - <region> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[region]"]</selector> - <strategy>css selector</strategy> - </region> - <postcode> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[postcode]"]</selector> - <strategy>css selector</strategy> - </postcode> - <telephone> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[telephone]"]</selector> - <strategy>css selector</strategy> - </telephone> - <fax> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[fax]"]</selector> - <strategy>css selector</strategy> - </fax> - <vat_id> - <selector>.address-item-edit:not([style="display: none;"]) [name$="[vat_id]"]</selector> - <strategy>css selector</strategy> - </vat_id> - <default_billing> - <selector>.ui-state-active [name$="[default_billing]"]</selector> - <input>checkbox</input> - </default_billing> - <default_shipping> - <selector>.ui-state-active [name$="[default_shipping]"]</selector> - <input>checkbox</input> - </default_shipping> - </fields> -</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.php new file mode 100644 index 0000000000000..029836d21433c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses; + +use Magento\Mtf\Client\Locator; +use Magento\Mtf\Block\Form; + +/** + * Create/Edit customer address. + */ +class AddressForm extends Form +{ + /** + * Save address button + * + * @var string + */ + protected $saveAddressButton = '#save'; + + /** + * cancel button + * + * @var string + */ + protected $cancelButton = '#cancel'; + + /** + * Loader mask + * + * @var string + */ + private $loader = '.popup-loading'; + + /** + * Field with Mage error. + * + * @var string + */ + private $mageErrorField = '//fieldset/*[contains(@class,"field ")][.//*[contains(@class,"error")]]'; + + /** + * Fields label with mage error. + * + * @var string + */ + private $mageErrorLabel = './/*[contains(@class,"label")]'; + + /** + * Mage error text. + * + * @var string + */ + private $mageErrorText = './/label[contains(@class,"error")]'; + + /** + * Fill address form by provided data + * + * @param \Magento\Mtf\Fixture\FixtureInterface $address + * @return void + * @throws \Exception + */ + public function fillAddressData(\Magento\Mtf\Fixture\FixtureInterface $address) + { + $this->waitForElementNotVisible($this->loader); + $this->setFieldsData($address->getData(), $this->_rootElement); + } + + /** + * Fill data into fields in the container. + * + * @param array $fields + * @param \Magento\Mtf\Client\Element\SimpleElement|null $contextElement + * @return void + * @throws \Exception + */ + public function setFieldsData(array $fields, \Magento\Mtf\Client\Element\SimpleElement $contextElement = null): void + { + $data = $this->dataMapping($fields); + $this->_fill($data, $contextElement); + } + + /** + * Save customer address + * + * @return void + */ + public function saveAddress(): void + { + $this->_rootElement->find($this->saveAddressButton)->click(); + $this->waitForElementNotVisible($this->loader); + } + + /** + * Close create/update address modal + * + * @return void + */ + public function clickCancelButton(): void + { + $this->_rootElement->find($this->cancelButton)->click(); + } + + /** + * Get array of label => js error text. + * + * @return array + */ + public function getJsErrors(): array + { + $data = []; + $elements = $this->_rootElement->getElements($this->mageErrorField, Locator::SELECTOR_XPATH); + foreach ($elements as $element) { + $error = $element->find($this->mageErrorText, Locator::SELECTOR_XPATH); + if ($error->isVisible()) { + $label = $element->find($this->mageErrorLabel, Locator::SELECTOR_XPATH)->getText(); + $data[$label] = $error->getText(); + } + } + return $data; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.xml new file mode 100644 index 0000000000000..c56c6479dcb38 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressForm.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping strict="0"> + <fields> + <prefix> + <selector>.admin__fieldset:not([style="display: none;"]) [name="prefix"]</selector> + <strategy>css selector</strategy> + </prefix> + <firstname> + <selector>.admin__fieldset:not([style="display: none;"]) [name="firstname"]</selector> + <strategy>css selector</strategy> + </firstname> + <middlename> + <selector>.admin__fieldset:not([style="display: none;"]) [name="middlename"]</selector> + <strategy>css selector</strategy> + </middlename> + <lastname> + <selector>.admin__fieldset:not([style="display: none;"]) [name="lastname"]</selector> + <strategy>css selector</strategy> + </lastname> + <suffix> + <selector>.admin__fieldset:not([style="display: none;"]) [name="suffix"]</selector> + <strategy>css selector</strategy> + </suffix> + <company> + <selector>.admin__fieldset:not([style="display: none;"]) [name="company"]</selector> + <strategy>css selector</strategy> + </company> + <street> + <selector>.admin__fieldset:not([style="display: none;"]) [name="street[0]"]</selector> + <strategy>css selector</strategy> + </street> + <city> + <selector>.admin__fieldset:not([style="display: none;"]) [name="city"]</selector> + <strategy>css selector</strategy> + </city> + <country_id> + <input>select</input> + <selector>.admin__fieldset:not([style="display: none;"]) [name="country_id"]</selector> + <strategy>css selector</strategy> + </country_id> + <region_id> + <input>select</input> + <selector>.admin__fieldset:not([style="display: none;"]) [name="region_id"]</selector> + <strategy>css selector</strategy> + </region_id> + <region> + <selector>.admin__fieldset:not([style="display: none;"]) [name="region"]</selector> + <strategy>css selector</strategy> + </region> + <postcode> + <selector>.admin__fieldset:not([style="display: none;"]) [name="postcode"]</selector> + <strategy>css selector</strategy> + </postcode> + <telephone> + <selector>.admin__fieldset:not([style="display: none;"]) [name="telephone"]</selector> + <strategy>css selector</strategy> + </telephone> + <fax> + <selector>.admin__fieldset:not([style="display: none;"]) [name="fax"]</selector> + <strategy>css selector</strategy> + </fax> + <vat_id> + <selector>.admin__fieldset:not([style="display: none;"]) [name="vat_id"]</selector> + <strategy>css selector</strategy> + </vat_id> + <default_billing> + <selector>.admin__fieldset:not([style="display: none;"]) input[name='default_billing']</selector> + <input>switcher</input> + </default_billing> + <default_shipping> + <selector>.admin__fieldset:not([style="display: none;"]) input[name='default_shipping']</selector> + <input>switcher</input> + </default_shipping> + </fields> +</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressesGrid.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressesGrid.php new file mode 100644 index 0000000000000..ef1fb09c8f839 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Adminhtml/Edit/Tab/Addresses/AddressesGrid.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Block\Adminhtml\Edit\Tab\Addresses; + +use Magento\Ui\Test\Block\Adminhtml\DataGrid; + +/** + * Class AddressesGrid + * Backend customer addresses grid + * + */ +class AddressesGrid extends DataGrid +{ + /** + * Locator value for link in action column + * + * @var string + */ + protected $editLink = '//tr[@class="data-row"][1]//a[@data-action="item-edit"]'; + + /** + * First row selector + * + * @var string + */ + protected $firstRowSelector = '//tr[@class="data-row"][1]'; + + /** + * Customer address grid loader. + * + * @var string + */ + protected $loader = '.customer_form_areas_address_address_customer_address_listing [data-role="spinner"]'; + + /** + * Filters array mapping + * + * @var array + */ + protected $filters = [ + 'firstname' => [ + 'selector' => '.admin__data-grid-filters input[name*=firstname]', + ], + 'lastname' => [ + 'selector' => '.admin__data-grid-filters input[name*=lastname]', + ], + 'street' => [ + 'selector' => '.admin__data-grid-filters input[name*=street]', + ], + 'city' => [ + 'selector' => '.admin__data-grid-filters input[name*=city]', + ], + 'region_id' => [ + 'selector' => '.admin__data-grid-filters input[name*=region]', + ], + 'postcode' => [ + 'selector' => '.admin__data-grid-filters input[name*=postcode]', + ], + 'telephone' => [ + 'selector' => '.admin__data-grid-filters input[name*=telephone]', + ], + 'country_id' => [ + 'selector' => '.admin__data-grid-filters select[name*=country]', + 'input' => 'select', + ], + + ]; + + /** + * Select action toggle. + * + * @var string + */ + private $selectAction = '.action-select'; + + /** + * Delete action toggle. + * + * @var string + */ + private $deleteAddress = '[data-action="item-delete"]'; + + /** + * Locator value for "Edit" link inside action column. + * + * @var string + */ + private $editAddress = '[data-action="item-edit"]'; + + /** + * Customer address modal window. + * + * @var string + */ + private $customerAddressModalForm = '.customer_form_areas_address_address_customer_address_update_modal'; + + /** + * Search customer address by filter. + * + * @param array $filter + * @return void + */ + public function search(array $filter): void + { + parent::search(array_intersect_key($filter, $this->filters)); + } + + /** + * Delete customer address by filter + * + * @param array $filter + * @return void + * @throws \Exception + */ + public function deleteCustomerAddress(array $filter): void + { + $this->search($filter); + $rowItem = $this->getRow([$filter['firstname']]); + if ($rowItem->isVisible()) { + $this->deleteRowItemAddress($rowItem); + } else { + throw new \Exception("Searched item was not found by filter\n" . print_r($filter, true)); + } + } + + /** + * @param \Magento\Mtf\Client\Element\SimpleElement $rowItem + * @return void + */ + public function deleteRowItemAddress(\Magento\Mtf\Client\Element\SimpleElement $rowItem): void + { + $rowItem->find($this->selectAction)->click(); + $rowItem->find($this->deleteAddress)->click(); + $modalElement = $this->browser->find($this->confirmModal); + /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ + $modal = $this->blockFactory->create( + \Magento\Ui\Test\Block\Adminhtml\Modal::class, + ['element' => $modalElement] + ); + $modal->acceptAlert(); + $this->waitLoader(); + } + + /** + * Open first row from the addresses grid + * + * @return void + */ + public function openFirstRow(): void + { + $firstRow = $this->getFirstRow(); + if ($firstRow->isVisible()) { + $firstRow->find($this->selectAction)->click(); + $firstRow->find($this->editAddress)->click(); + $this->waitForElementVisible($this->customerAddressModalForm); + $this->waitLoader(); + } + } + + /** + * Get first row from the grid + * + * @return \Magento\Mtf\Client\Element\SimpleElement + */ + public function getFirstRow(): \Magento\Mtf\Client\Element\SimpleElement + { + return $this->_rootElement->find($this->firstRowSelector, \Magento\Mtf\Client\Locator::SELECTOR_XPATH); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php index abfee067a73de..4d086cf06053d 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php @@ -28,7 +28,7 @@ public function processAssert(CustomerAccountIndex $customerAccountIndex, Addres $customerAccountIndex->getAccountMenuBlock()->openMenuItem('Address Book'); $addressRenderer = $this->objectManager->create( \Magento\Customer\Test\Block\Address\Renderer::class, - ['address' => $shippingAddress, 'type' => 'html'] + ['address' => $shippingAddress, 'type' => 'html_without_company_separated_names'] )->render(); $isAddressExists = $customerAccountIndex->getAdditionalAddressBlock() ->isAdditionalAddressExists($addressRenderer); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertCustomerAddressBackendRequiredFields.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertCustomerAddressBackendRequiredFields.php new file mode 100644 index 0000000000000..46bb8ce72abb1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertCustomerAddressBackendRequiredFields.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Constraint; + +use Magento\Customer\Test\Page\Adminhtml\CustomerIndexNew; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert required fields on customer address form. + */ +class AssertCustomerAddressBackendRequiredFields extends AbstractConstraint +{ + /** + * Expected message. + */ + const REQUIRE_MESSAGE = 'This is a required field.'; + + /** + * Assert required fields on customer address form. + * @param CustomerIndexNew $customerNewPage + * @param array $expectedRequiredFields + * @return void + * @throws \Exception + */ + public function processAssert(CustomerIndexNew $customerNewPage, array $expectedRequiredFields): void + { + $actualRequiredFields = $customerNewPage->getCustomerForm()->getTab('addresses') + ->getCustomerAddressModalForm()->getJsErrors(); + foreach ($expectedRequiredFields as $field) { + \PHPUnit\Framework\Assert::assertTrue( + isset($actualRequiredFields[$field]), + "Field '$field' is not highlighted with an JS error." + ); + \PHPUnit\Framework\Assert::assertEquals( + self::REQUIRE_MESSAGE, + $actualRequiredFields[$field], + "Field '$field' is not highlighted as required." + ); + } + } + + /** + * Return string representation of object. + * + * @return string + */ + public function toString(): string + { + return 'All required fields on customer form are highlighted.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php index f1a8b17ae2855..ccc6da81038f8 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php @@ -112,15 +112,15 @@ public function __inject( * Create customer on backend. * * @param Customer $customer - * @param string $customerAction - * @param Address $address + * @param null $customerAction + * @param Address|null $address * @param array $steps * @param array $beforeActionCallback - * @return void + * @throws \Exception */ public function test( Customer $customer, - $customerAction, + $customerAction = null, Address $address = null, array $steps = [], array $beforeActionCallback = [] @@ -135,7 +135,12 @@ public function test( $this->pageCustomerIndex->open(); $this->pageCustomerIndex->getPageActionsBlock()->addNew(); - $this->pageCustomerIndexNew->getCustomerForm()->fillCustomer($customer, $address); + $this->pageCustomerIndexNew->getCustomerForm()->fillCustomer($customer); + if (null !== $address) { + $this->pageCustomerIndexNew->getPageActionsBlock()->saveAndContinue(); + $this->pageCustomerIndexNew->getMessagesBlock()->waitSuccessMessage(); + $this->pageCustomerIndexNew->getCustomerForm()->fillCustomerAddress($address); + } $this->address = $address; $this->customer = $customer; @@ -144,8 +149,9 @@ public function test( call_user_func([$this, $methodName]); } } - - $this->pageCustomerIndexNew->getPageActionsBlock()->$customerAction(); + if (null !== $customerAction) { + $this->pageCustomerIndexNew->getPageActionsBlock()->$customerAction(); + } } /** diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml index 502ac2495357a..5bb96dc13c739 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml @@ -115,8 +115,7 @@ </data> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerBackendRequiredFields" /> </variation> - <variation name="CreateCustomerBackendEntityTestVariation9" summary="Verify required fields on Addresses tab."> - <data name="customerAction" xsi:type="string">save</data> + <variation name="CreateCustomerBackendEntityTestVariation9" summary="Verify required fields on the Add address modal."> <data name="customer/data/website_id" xsi:type="string">Main Website</data> <data name="customer/data/group_id/dataset" xsi:type="string">General</data> <data name="customer/data/firstname" xsi:type="string">John%isolation%</data> @@ -131,7 +130,7 @@ <item name="5" xsi:type="string">Zip/Postal Code</item> <item name="6" xsi:type="string">Phone Number</item> </data> - <constraint name="Magento\Customer\Test\Constraint\AssertCustomerBackendRequiredFields" /> + <constraint name="Magento\Customer\Test\Constraint\AssertCustomerAddressBackendRequiredFields" /> </variation> <variation name="CreateCustomerBackendEntityTestVariation10" summary="Create customer with 2 websites and with different allowed countries."> <data name="tag" xsi:type="string">to_maintain:yes</data> diff --git a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/Constraint/AssertImportCustomerAddresses.php b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/Constraint/AssertImportCustomerAddresses.php index 18b2d44a22ff1..f73609b3ba2cf 100644 --- a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/Constraint/AssertImportCustomerAddresses.php +++ b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/Constraint/AssertImportCustomerAddresses.php @@ -116,8 +116,7 @@ private function getPrepareAddresses() foreach ($customers as $customer) { $this->customerIndexEdit->open(['id' => $customer->getId()]); $customerForm->openTab('addresses'); - $addresses = $customerForm->getTab('addresses')->getDataAddresses($addressTemplate); - $address = array_shift($addresses); + $address = $customerForm->getTab('addresses')->getAddressFromFirstRow($addressTemplate); if (!empty($address)) { $resultAddressesArray[] = $address; } diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml index dce1358a1ecf4..59c00683e3b1a 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml @@ -58,7 +58,7 @@ <field name="sku" is_required="1" group="product-details" /> <field name="small_image" is_required="0" /> <field name="small_image_label" is_required="0" /> - <field name="status" is_required="0" /> + <field name="status" is_required="0" group="product-details" /> <field name="thumbnail" is_required="0" /> <field name="thumbnail_label" is_required="0" /> <field name="updated_at" is_required="1" /> @@ -66,7 +66,7 @@ <field name="upsell_tgtr_position_limit" is_required="0" /> <field name="url_key" is_required="0" group="search-engine-optimization" /> <field name="url_path" is_required="0" /> - <field name="visibility" is_required="0" /> + <field name="visibility" is_required="0" group="product-details" /> <field name="id" /> <field name="type_id" /> <field name="attribute_set_id" group="product-details" source="Magento\Catalog\Test\Fixture\Product\AttributeSetId" /> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml index f63e7282719d2..15e0b8a27d24e 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> - <page name="DevdocsInstall" mca="http://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" module="Magento_Install"> + <page name="DevdocsInstall" mca="https://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" module="Magento_Install"> <block name="devdocsBlock" class="Magento\Install\Test\Block\Devdocs" locator="body" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php similarity index 88% rename from dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php rename to dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php index ed7596bf72f76..2c87d8539f0db 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php @@ -11,10 +11,10 @@ use Magento\Mtf\Constraint\AbstractConstraint; /** - * Class AssertOrderCancelAndSuccessMassActionFailMessage + * Class AssertOrderCancelMassActionFailMessage * Assert cancel fail message is displayed on order index page */ -class AssertOrderCancelMassActionPartialFailMessage extends AbstractConstraint +class AssertOrderCancelMassActionFailMessage extends AbstractConstraint { /** * Text value to be checked diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php index c3e8558df7fcc..fc854bd8c50ad 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php @@ -39,8 +39,8 @@ public function processAssert( /** @var \Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Info $infoTab */ $infoTab = $salesOrderView->getOrderForm()->openTab('info')->getTab('info'); \PHPUnit\Framework\Assert::assertEquals( - $infoTab->getOrderStatus(), - $orderStatus + $orderStatus, + $infoTab->getOrderStatus() ); } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php index 073fb0967144c..972dc0f510b36 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php @@ -200,7 +200,7 @@ protected function setBillingAddress(OrderInjectable $order) $this->webapiTransport->close(); if (!is_numeric($response)) { $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); - throw new \Exception("Could not set billing addresss to quote!"); + throw new \Exception("Could not set billing address to quote!"); } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml index 99e25b062846b..1f75b07c8ca1e 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\MassOrdersUpdateTest" summary="Mass Update Orders" ticketId="MAGETWO-27897"> <variation name="MassOrdersUpdateTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">cancel orders in status Pending and Processing</data> <data name="steps" xsi:type="string">-</data> <data name="action" xsi:type="string">Cancel</data> @@ -23,16 +22,16 @@ <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> <data name="resultStatuses" xsi:type="string">Complete,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation3"> - <data name="description" xsi:type="string">try to cancel orders in status Pending, Closed</data> + <data name="description" xsi:type="string">try to cancel orders in status Processing, Closed</data> <data name="steps" xsi:type="string">invoice|invoice, credit memo</data> <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> <data name="resultStatuses" xsi:type="string">Processing,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation4"> @@ -45,7 +44,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation5"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">Try to put order in status Complete on Hold</data> <data name="steps" xsi:type="string">invoice, shipment</data> <data name="action" xsi:type="string">Hold</data> diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml b/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml index 13c7051d0c1ba..733b110ec5494 100644 --- a/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml +++ b/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml @@ -8,8 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Search\Test\TestCase\AdvancedSearchWithAttributeTest" summary="Use Advanced Search by Decimal indexable attribute if Edit/Add Attribute" ticketId="MAGETWO-25931"> <variation name="AdvancedSearchWithWeightAttributeTestVariation1"> - <data name="issue" xsi:type="string">MAGETWO-65408: [FT] Magento\Search\Test\TestCase\AdvancedSearchWithAttributeTest fails on Jenkins</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="productDropDownList/0" xsi:type="string">configurable</data> <data name="productDropDownList/1" xsi:type="string">simple</data> <data name="productDropDownList/2" xsi:type="string">bundle</data> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/CreateTaxRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/CreateTaxRuleEntityTest.xml index fe4ea7a0fb07a..cf4e54adac0c9 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/CreateTaxRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/CreateTaxRuleEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Tax\Test\TestCase\CreateTaxRuleEntityTest" summary="Create Tax Rule " ticketId="MAGETWO-20913"> <variation name="CreateTaxRuleEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="taxRule/data/code" xsi:type="string">TaxIdentifier%isolation%</data> <data name="taxRule/data/tax_rate/dataset/rate_0" xsi:type="string">US-CA-Rate_1</data> <data name="taxRule/data/tax_customer_class/dataset/class_0" xsi:type="string">-</data> @@ -19,6 +20,7 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleForm" /> </variation> <variation name="CreateTaxRuleEntityTestVariation2"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="tag" xsi:type="string">stable:no</data> <data name="taxRule/data/code" xsi:type="string">TaxIdentifier%isolation%</data> <data name="taxRule/data/tax_rate/dataset/rate_0" xsi:type="string">US-CA-Rate_1</data> @@ -34,6 +36,7 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleForm" /> </variation> <variation name="CreateTaxRuleEntityTestVariation3"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Creating tax rule with new tax classes and tax rate</data> <data name="taxRule/data/code" xsi:type="string">TaxIdentifier%isolation%</data> <data name="taxRule/data/tax_rate/dataset/rate_0" xsi:type="string">default</data> @@ -48,6 +51,7 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleForm" /> </variation> <variation name="CreateTaxRuleEntityTestVariation4"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="taxRule/data/code" xsi:type="string">TaxIdentifier%isolation%</data> <data name="taxRule/data/tax_rate/dataset/rate_0" xsi:type="string">withZipRange</data> <data name="taxRule/data/tax_rate/dataset/rate_1" xsi:type="string">US-CA-Rate_1</data> @@ -61,6 +65,7 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleForm" /> </variation> <variation name="CreateTaxRuleEntityTestVariation5" summary="Create Tax Rule with New and Existing Tax Rate, Customer Tax Class, Product Tax Class" ticketId="MAGETWO-12438"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> <data name="taxRule/data/code" xsi:type="string">TaxIdentifier%isolation%</data> <data name="taxRule/data/tax_rate/dataset/rate_0" xsi:type="string">US-CA-*-Rate 1</data> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRateEntityTest.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRateEntityTest.xml index 5d80acd8a003a..40270d84199cb 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRateEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRateEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Tax\Test\TestCase\DeleteTaxRateEntityTest" summary="Delete Tax Rate" ticketId="MAGETWO-23295"> <variation name="DeleteTaxRateEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="taxRate/dataset" xsi:type="string">default</data> <constraint name="Magento\Tax\Test\Constraint\AssertTaxRateSuccessDeleteMessage" /> <constraint name="Magento\Tax\Test\Constraint\AssertTaxRateNotInGrid" /> diff --git a/dev/tests/integration/framework/tests/unit/phpunit.xml.dist b/dev/tests/integration/framework/tests/unit/phpunit.xml.dist index b52ca37987cb7..1a93397caaa4a 100644 --- a/dev/tests/integration/framework/tests/unit/phpunit.xml.dist +++ b/dev/tests/integration/framework/tests/unit/phpunit.xml.dist @@ -21,4 +21,32 @@ <php> <ini name="date.timezone" value="America/Los_Angeles"/> </php> + <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="@magentoDbIsolation"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + </array> + </arguments> + </listener> + </listeners> </phpunit> diff --git a/dev/tests/integration/phpunit.xml.dist b/dev/tests/integration/phpunit.xml.dist index dedc144c56799..b54a504a1bebc 100644 --- a/dev/tests/integration/phpunit.xml.dist +++ b/dev/tests/integration/phpunit.xml.dist @@ -77,5 +77,52 @@ <listeners> <listener class="Magento\TestFramework\Event\PhpUnit"/> <listener class="Magento\TestFramework\ErrorLog\Listener"/> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppArea"> + <string>magentoAppArea</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoCache"> + <string>magentoCache</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="magentoDataFixture"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDataFixtureBeforeTransaction"> + <string>magentoDataFixtureBeforeTransaction</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + <element key="magentoIndexerDimensionMode"> + <string>magentoIndexerDimensionMode</string> + </element> + </array> + </arguments> + </listener> </listeners> </phpunit> diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php new file mode 100644 index 0000000000000..91dcd5f3e8d5b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Bundle\Model\Plugin\Frontend; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Interception\PluginList; +use PHPUnit\Framework\TestCase; + +/** + * Test bundle fronted product plugin adds children products ids to bundle product identities. + */ +class ProductTest extends TestCase +{ + /** + * Check, product plugin is registered for storefront. + * + * @magentoAppArea frontend + * @return void + */ + public function testProductIsRegistered(): void + { + $pluginInfo = Bootstrap::getObjectManager()->get(PluginList::class) + ->get(\Magento\Catalog\Model\Product::class, []); + $this->assertSame(Product::class, $pluginInfo['bundle']['instance']); + } + + /** + * Check plugin will add children ids to bundle product identities on storefront. + * + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoAppArea frontend + * @return void + */ + public function testGetIdentitiesForBundleProductOnStorefront(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $bundleProduct = $productRepository->get('bundle-product'); + $simpleProduct = $productRepository->get('simple'); + $expectedIdentities = [ + 'cat_p_' . $bundleProduct->getId(), + 'cat_p', + 'cat_p_' . $simpleProduct->getId(), + + ]; + $this->assertEquals($expectedIdentities, $bundleProduct->getIdentities()); + } + + /** + * Check plugin won't add children ids to bundle product identities in admin area. + * + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoAppArea adminhtml + * @return void + */ + public function testGetIdentitiesForBundleProductInAdminArea(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $bundleProduct = $productRepository->get('bundle-product'); + $expectedIdentities = [ + 'cat_p_' . $bundleProduct->getId(), + ]; + $this->assertEquals($expectedIdentities, $bundleProduct->getIdentities()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php index dad4d21362a0a..1001d58ee8a67 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php @@ -6,12 +6,35 @@ namespace Magento\Catalog\Controller\Adminhtml; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Store\Model\Store; +use Magento\Catalog\Model\ResourceModel\Product; /** * @magentoAppArea adminhtml */ class CategoryTest extends \Magento\TestFramework\TestCase\AbstractBackendController { + /** + * @var \Magento\Catalog\Model\ResourceModel\Product + */ + protected $productResource; + + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ + protected function setUp() + { + parent::setUp(); + + /** @var Product $productResource */ + $this->productResource = Bootstrap::getObjectManager()->get( + Product::class + ); + } + /** * @magentoDataFixture Magento/Store/_files/core_fixturestore.php * @magentoDbIsolation enabled @@ -408,4 +431,128 @@ public function moveActionDataProvider() [400, 401, 'first_url_key', 0, 'second_url_key', true], ]; } + + /** + * @magentoDataFixture Magento/Catalog/_files/products_in_different_stores.php + * @magentoDbIsolation disabled + * @dataProvider saveActionWithDifferentWebsitesDataProvider + * + * @param array $postData + */ + public function testSaveCategoryWithProductPosition(array $postData) + { + /** @var $store \Magento\Store\Model\Store */ + $store = Bootstrap::getObjectManager()->create(Store::class); + $store->load('fixturestore', 'code'); + $storeId = $store->getId(); + $oldCategoryProductsCount = $this->getCategoryProductsCount(); + $this->getRequest()->setParam('store', $storeId); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParam('id', 96377); + $this->getRequest()->setPostValue($postData); + $this->dispatch('backend/catalog/category/save'); + $newCategoryProductsCount = $this->getCategoryProductsCount(); + $this->assertEquals( + $oldCategoryProductsCount, + $newCategoryProductsCount, + 'After changing product position number of records from catalog_category_product has changed' + ); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function saveActionWithDifferentWebsitesDataProvider() + { + return [ + 'default_values' => [ + [ + 'store_id' => '1', + 'entity_id' => '96377', + 'attribute_set_id' => '4', + 'parent_id' => '2', + 'created_at' => '2018-11-29 08:28:37', + 'updated_at' => '2018-11-29 08:57:43', + 'path' => '1/2/96377', + 'level' => '2', + 'children_count' => '0', + 'row_id' => '96377', + 'name' => 'Category 1', + 'display_mode' => 'PRODUCTS', + 'url_key' => 'category-1', + 'url_path' => 'category-1', + 'automatic_sorting' => '0', + 'is_active' => '1', + 'is_anchor' => '1', + 'include_in_menu' => '1', + 'custom_use_parent_settings' => '0', + 'custom_apply_to_products' => '0', + 'path_ids' => [ + 0 => '1', + 1 => '2', + 2 => '96377' + ], + 'use_config' => [ + 'available_sort_by' => 'true', + 'default_sort_by' => 'true', + 'filter_price_range' => 'true' + ], + 'id' => '', + 'parent' => '0', + 'use_default' => [ + 'name' => '1', + 'url_key' => '1', + 'meta_title' => '1', + 'is_active' => '1', + 'include_in_menu' => '1', + 'custom_use_parent_settings' => '1', + 'custom_apply_to_products' => '1', + 'description' => '1', + 'landing_page' => '1', + 'display_mode' => '1', + 'custom_design' => '1', + 'page_layout' => '1', + 'meta_keywords' => '1', + 'meta_description' => '1', + 'custom_layout_update' => '1', + 'image' => '1' + ], + 'filter_price_range' => false, + 'meta_title' => false, + 'url_key_create_redirect' => 'category-1', + 'description' => false, + 'landing_page' => false, + 'default_sort_by' => 'position', + 'available_sort_by' => false, + 'custom_design' => false, + 'page_layout' => false, + 'meta_keywords' => false, + 'meta_description' => false, + 'custom_layout_update' => false, + 'position_cache_key' => '5c069248346ac', + 'is_smart_category' => '0', + 'smart_category_rules' => false, + 'sort_order' => '0', + 'vm_category_products' => '{"1":1,"3":0}' + ] + ] + ]; + } + + /** + * Get items count from catalog_category_product + * + * @return int + */ + private function getCategoryProductsCount(): int + { + $oldCategoryProducts = $this->productResource->getConnection()->select()->from( + $this->productResource->getTable('catalog_category_product'), + 'product_id' + ); + return count( + $this->productResource->getConnection()->fetchAll($oldCategoryProducts) + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php index ab8c29889eabd..fe08ec01a9715 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php @@ -156,7 +156,7 @@ public function testWrongAttributeCode() /** @var \Magento\Framework\Message\Error $message */ $message = $messages->getItemsByType('error')[0]; $this->assertEquals( - 'Attribute code "_()&&&?" is invalid. Please use only letters (a-z),' + 'Attribute code "_()&&&?" is invalid. Please use only letters (a-z or A-Z),' . ' numbers (0-9) or underscore(_) in this field, first character should be a letter.', $message->getText() ); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php index 691ae78de71af..80af901788dd8 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php @@ -13,7 +13,7 @@ use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Catalog\Pricing\Price\TierPrice; -use Magento\Customer\Model\Group; +use Magento\Customer\Model\Group as CustomerGroup; /** * @group indexer_dimension @@ -56,7 +56,7 @@ public function testTierPrice() $tierPrice = $this->objectManager->create(ProductTierPriceInterfaceFactory::class) ->create(); - $tierPrice->setCustomerGroupId(Group::CUST_GROUP_ALL); + $tierPrice->setCustomerGroupId(CustomerGroup::CUST_GROUP_ALL); $tierPrice->setQty(1.00); $tierPrice->setValue($tierPriceValue); $tierPriceManagement = $this->objectManager->create(ScopedProductTierPriceManagementInterface::class); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php index 0ab74788bfd3b..973d290d61466 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php @@ -68,27 +68,49 @@ public function testValidate() [ ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 5, 'price' => 5], + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 5.6, 'price' => 4], ] ); $this->assertTrue($this->_model->validate($product)); } /** + * Test that duplicated tier price values issues exception during validation. + * + * @dataProvider validateDuplicateDataProvider * @expectedException \Magento\Framework\Exception\LocalizedException */ - public function testValidateDuplicate() + public function testValidateDuplicate(array $tierPricesData) { $product = new \Magento\Framework\DataObject(); - $product->setTierPrice( - [ - ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], - ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], - ] - ); + $product->setTierPrice($tierPricesData); $this->_model->validate($product); } + /** + * testValidateDuplicate data provider. + * + * @return array + */ + public function validateDuplicateDataProvider(): array + { + return [ + [ + [ + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], + ], + ], + [ + [ + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2.2, 'price' => 8], + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2.2, 'price' => 8], + ], + ], + ]; + } + /** * @expectedException \Magento\Framework\Exception\LocalizedException */ @@ -97,9 +119,9 @@ public function testValidateDuplicateWebsite() $product = new \Magento\Framework\DataObject(); $product->setTierPrice( [ - ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], - ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 5, 'price' => 5], - ['website_id' => 1, 'cust_group' => 1, 'price_qty' => 5, 'price' => 5], + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2.2, 'price' => 8], + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 5.3, 'price' => 5], + ['website_id' => 1, 'cust_group' => 1, 'price_qty' => 5.3, 'price' => 5], ] ); @@ -127,12 +149,17 @@ public function testPreparePriceData() ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'price' => 8], ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 5, 'price' => 5], ['website_id' => 1, 'cust_group' => 1, 'price_qty' => 5, 'price' => 5], + ['website_id' => 1, 'cust_group' => 1, 'price_qty' => 5.3, 'price' => 4], + ['website_id' => 1, 'cust_group' => 1, 'price_qty' => 5.4, 'price' => 3], + ['website_id' => 1, 'cust_group' => 1, 'price_qty' => '5.40', 'price' => 2], ]; $newData = $this->_model->preparePriceData($data, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, 1); - $this->assertEquals(2, count($newData)); + $this->assertEquals(4, count($newData)); $this->assertArrayHasKey('1-2', $newData); $this->assertArrayHasKey('1-5', $newData); + $this->assertArrayHasKey('1-5.3', $newData); + $this->assertArrayHasKey('1-5.4', $newData); } public function testAfterLoad() @@ -148,7 +175,7 @@ public function testAfterLoad() $this->_model->afterLoad($product); $price = $product->getTierPrice(); $this->assertNotEmpty($price); - $this->assertEquals(4, count($price)); + $this->assertEquals(5, count($price)); } /** @@ -187,6 +214,7 @@ public function saveExistingProductDataProvider(): array ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 2, 'value' => 8], ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 5, 'value' => 5], ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3, 'value' => 5], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3.2, 'value' => 6], [ 'website_id' => 0, 'customer_group_id' => 0, @@ -194,13 +222,14 @@ public function saveExistingProductDataProvider(): array 'extension_attributes' => new \Magento\Framework\DataObject(['percentage_value' => 50]) ], ], - 4, + 5, ], 'update one' => [ [ ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 2, 'value' => 8], ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 5, 'value' => 5], ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3, 'value' => 5], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => '3.2', 'value' => 6], [ 'website_id' => 0, 'customer_group_id' => 0, @@ -208,12 +237,13 @@ public function saveExistingProductDataProvider(): array 'extension_attributes' => new \Magento\Framework\DataObject(['percentage_value' => 10]) ], ], - 4, + 5, ], 'delete one' => [ [ ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 5, 'value' => 5], ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3, 'value' => 5], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => '3.2', 'value' => 6], [ 'website_id' => 0, 'customer_group_id' => 0, @@ -221,13 +251,14 @@ public function saveExistingProductDataProvider(): array 'extension_attributes' => new \Magento\Framework\DataObject(['percentage_value' => 50]) ], ], - 3, + 4, ], 'add one' => [ [ ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 2, 'value' => 8], ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 5, 'value' => 5], ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3, 'value' => 5], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3.2, 'value' => 6], [ 'website_id' => 0, 'customer_group_id' => 32000, @@ -241,7 +272,7 @@ public function saveExistingProductDataProvider(): array 'extension_attributes' => new \Magento\Framework\DataObject(['percentage_value' => 50]) ], ], - 5, + 6, ], 'delete all' => [[], 0,], ]; @@ -270,7 +301,7 @@ public function testSaveNewProduct(array $tierPricesData, int $tierPriceCount): $tierPrices = []; foreach ($tierPricesData as $tierPrice) { $tierPrices[] = $this->tierPriceFactory->create([ - 'data' => $tierPrice + 'data' => $tierPrice, ]); } $product->setTierPrices($tierPrices); @@ -290,6 +321,8 @@ public function saveNewProductDataProvider(): array ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 2, 'value' => 8], ['website_id' => 0, 'customer_group_id' => 32000, 'qty' => 5, 'value' => 5], ['website_id' => 0, 'customer_group_id' => 0, 'qty' => 3, 'value' => 5], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => '3.2', 'value' => 4], + ['website_id' => 0, 'customer_group_id' => 0, 'qty' => '3.3', 'value' => 3], [ 'website_id' => 0, 'customer_group_id' => 0, @@ -297,7 +330,7 @@ public function saveNewProductDataProvider(): array 'extension_attributes' => new \Magento\Framework\DataObject(['percentage_value' => 50]) ], ], - 4, + 6, ], ]; } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php index 8370e514dc2f2..a1923e63972ee 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductExternalTest.php @@ -69,7 +69,7 @@ public function testGetCategoryId() { $this->assertFalse($this->_model->getCategoryId()); $category = new \Magento\Framework\DataObject(['id' => 5]); - + $this->_model->setCategoryIds([5]); $this->objectManager->get(\Magento\Framework\Registry::class)->register('current_category', $category); try { $this->assertEquals(5, $this->_model->getCategoryId()); @@ -83,6 +83,7 @@ public function testGetCategoryId() public function testGetCategory() { $this->assertEmpty($this->_model->getCategory()); + $this->_model->setCategoryIds([3]); $this->objectManager->get(\Magento\Framework\Registry::class) ->register('current_category', new \Magento\Framework\DataObject(['id' => 3])); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php index 82fe2e1f30283..514c6563622c9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php @@ -60,6 +60,16 @@ ] )->setExtensionAttributes($tierPriceExtensionAttributes1); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 3.2, + 'value' => 6, + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes1); + $tierPriceExtensionAttributes2 = $tpExtensionAttributesFactory->create() ->setWebsiteId($adminWebsite->getId()) ->setPercentageValue(50); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores.php new file mode 100644 index 0000000000000..6102738bd6be4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Api\ProductRepositoryInterface; + +require __DIR__ . '/../../Store/_files/core_fixturestore.php'; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Magento\Store\Model\Website $website */ +$website = $objectManager->get(Magento\Store\Model\Website::class); + +$website->setData( + [ + 'code' => 'second_website', + 'name' => 'Test Website', + ] +); + +$website->save(); + +$objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->reinitStores(); + +/** @var IndexerRegistry $indexerRegistry */ +$indexerRegistry = $objectManager->create(IndexerRegistry::class); +$indexer = $indexerRegistry->get('catalogsearch_fulltext'); + +$indexer->reindexAll(); + +$category = $objectManager->create(\Magento\Catalog\Model\Category::class); +$category->isObjectNew(true); +$category->setId(96377) + ->setName('Category 1') + ->setParentId(2) + ->setPath('1/2/96377') + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple_1') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([96377]); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([$website->getId()]) + ->setName('Simple Product 2') + ->setSku('simple_2') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([96377]); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1, $website->getId()]) + ->setName('Simple Product 3') + ->setSku('simple_3') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([96377]); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores_rollback.php new file mode 100644 index 0000000000000..9b957b75eb2a3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_in_different_stores_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../Store/_files/core_fixturestore_rollback.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +//Remove category +/** @var $category \Magento\Catalog\Model\Category */ +$category = $objectManager->create(\Magento\Catalog\Model\Category::class); +$category->load(96377); +if ($category->getId()) { + $category->delete(); +} + +$productSkuList = ['simple_1', 'simple_2', 'simple_3']; +foreach ($productSkuList as $sku) { + try { + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $product = $productRepository->get($sku, true); + if ($product->getId()) { + $productRepository->delete($product); + } + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed + } +} + +/** @var Magento\Store\Model\Website $website */ +$website = $objectManager->get(Magento\Store\Model\Website::class); +$website->load('second_website', 'code'); +if ($website->getId()) { + $website->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index 70e0cf0e1e74e..c212d4c0d971a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -70,7 +70,7 @@ protected function setUp() /** * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php - * @magentoDbIsolationEnabled + * @magentoDbIsolation enabled */ public function testExport() { @@ -95,7 +95,7 @@ public function testExport() /** * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data_special_chars.php - * @magentoDbIsolationEnabled + * @magentoDbIsolation enabled */ public function testExportSpecialChars() { @@ -111,7 +111,7 @@ public function testExportSpecialChars() /** * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_product_links_data.php - * @magentoDbIsolationEnabled + * @magentoDbIsolation enabled */ public function testExportWithProductLinks() { 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 2da29ec807d5c..e5d97ac0e6844 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -762,7 +762,6 @@ protected function getOptionValues(\Magento\Catalog\Model\Product\Option $option /** * Test that product import with images works properly * - * @magentoDataIsolation enabled * @magentoDataFixture mediaImportImageFixture * @magentoAppIsolation enabled * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -813,7 +812,6 @@ public function testSaveMediaImage() /** * Test that errors occurred during importing images are logged. * - * @magentoDataIsolation enabled * @magentoAppIsolation enabled * @magentoDataFixture mediaImportImageFixture * @magentoDataFixture mediaImportImageFixtureError @@ -2212,7 +2210,6 @@ public function testImportWithDifferentSkuCase() /** * Test that product import with images for non-default store works properly. * - * @magentoDataIsolation enabled * @magentoDataFixture mediaImportImageFixture * @magentoAppIsolation enabled */ diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php index 186c6b8a92bb1..11cc73e2cf944 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php @@ -10,6 +10,14 @@ */ class ProductTest extends AbstractProductExportImportTestCase { + /** + * Set up + */ + protected function setUp() + { + $this->markTestSkipped('MAGETWO-97378'); + } + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php index 59ad91ae7b076..56c5db5572a31 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php @@ -14,6 +14,9 @@ use Magento\Catalog\Model\Product; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class for testing fulltext index rebuild + */ class FullTest extends \PHPUnit\Framework\TestCase { /** @@ -21,6 +24,9 @@ class FullTest extends \PHPUnit\Framework\TestCase */ protected $actionFull; + /** + * @inheritdoc + */ protected function setUp() { $this->actionFull = Bootstrap::getObjectManager()->create( @@ -29,6 +35,8 @@ protected function setUp() } /** + * Testing fulltext index rebuild + * * @magentoDataFixture Magento/CatalogSearch/_files/products_for_index.php * @magentoDataFixture Magento/CatalogSearch/_files/product_configurable_not_available.php * @magentoDataFixture Magento/Framework/Search/_files/product_configurable.php @@ -39,7 +47,6 @@ public function testGetIndexData() $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); $allowedStatuses = Bootstrap::getObjectManager()->get(Status::class)->getVisibleStatusIds(); $allowedVisibility = Bootstrap::getObjectManager()->get(Engine::class)->getAllowedVisibility(); - $result = iterator_to_array($this->actionFull->rebuildStoreIndex(Store::DISTRO_STORE_ID)); $this->assertNotEmpty($result); @@ -58,7 +65,10 @@ public function testGetIndexData() } /** + * Prepare and return expected index data + * * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getExpectedIndexData() { @@ -68,32 +78,48 @@ private function getExpectedIndexData() $nameId = $attributeRepository->get(ProductInterface::NAME)->getAttributeId(); /** @see dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php */ $configurableId = $attributeRepository->get('test_configurable')->getAttributeId(); + $statusId = $attributeRepository->get(ProductInterface::STATUS)->getAttributeId(); + $taxClassId = $attributeRepository + ->get(\Magento\Customer\Api\Data\GroupInterface::TAX_CLASS_ID) + ->getAttributeId(); return [ 'configurable' => [ $skuId => 'configurable', $configurableId => 'Option 1 | Option 2', $nameId => 'Configurable Product | Configurable OptionOption 1 | Configurable OptionOption 2', + $taxClassId => 'Taxable Goods | Taxable Goods | Taxable Goods', + $statusId => 'Enabled | Enabled | Enabled' ], 'index_enabled' => [ $skuId => 'index_enabled', $nameId => 'index enabled', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_search' => [ $skuId => 'index_visible_search', $nameId => 'index visible search', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_category' => [ $skuId => 'index_visible_category', $nameId => 'index visible category', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_both' => [ $skuId => 'index_visible_both', $nameId => 'index visible both', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ] ]; } /** + * Testing fulltext index rebuild with configurations + * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */ public function testRebuildStoreIndexConfigurable() @@ -114,6 +140,8 @@ public function testRebuildStoreIndexConfigurable() } /** + * Get product Id by its SKU + * * @param string $sku * @return int */ diff --git a/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php index f68f3cf37b079..73f1dd9e7b711 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php @@ -28,7 +28,7 @@ /** * Tests the different flows of config:set command. * - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoDbIsolation enabled */ @@ -292,8 +292,7 @@ public function runExtendedDataProvider() * @param string $scope * @param $scopeCode string|null * @dataProvider configSetValidationErrorDataProvider - * - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testConfigSetValidationError( $path, @@ -307,6 +306,7 @@ public function testConfigSetValidationError( /** * Data provider for testConfigSetValidationError + * * @return array */ public function configSetValidationErrorDataProvider() @@ -399,7 +399,6 @@ public function testConfigSetCurrency() * Saving values with successful validation * * @dataProvider configSetValidDataProvider - * * @magentoDbIsolation enabled */ public function testConfigSetValid() diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php new file mode 100644 index 0000000000000..1fffd701c509f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Interception\PluginList; +use PHPUnit\Framework\TestCase; + +/** + * Test configurable fronted product plugin will add children products ids to configurable product identities. + */ +class ProductIdentitiesExtenderTest extends TestCase +{ + /** + * Check, product identities extender plugin is registered for storefront. + * + * @magentoAppArea frontend + * @return void + */ + public function testIdentitiesExtenderIsRegistered(): void + { + $pluginInfo = Bootstrap::getObjectManager()->get(PluginList::class) + ->get(\Magento\Catalog\Model\Product::class, []); + $this->assertSame(ProductIdentitiesExtender::class, $pluginInfo['product_identities_extender']['instance']); + } + + /** + * Check plugin will add children ids to configurable product identities on storefront. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea frontend + * @return void + */ + public function testGetIdentitiesForConfigurableProductOnStorefront(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $simpleProduct1 = $productRepository->get('simple_10'); + $simpleProduct2 = $productRepository->get('simple_20'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + 'cat_p', + 'cat_p_' . $simpleProduct1->getId(), + 'cat_p_' . $simpleProduct2->getId(), + + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); + } + + /** + * Check plugin won't add children ids to configurable product identities in admin area. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea adminhtml + * @return void + */ + public function testGetIdentitiesForConfigurableProductInAdminArea(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php index 66091c108f0b2..32eddb28151a7 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php @@ -12,11 +12,18 @@ use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use PHPUnit\Framework\TestCase; +use Magento\Catalog\Api\Data\ProductInterface; /** + * Configurable test + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoAppArea adminhtml */ -class ConfigurableTest extends \PHPUnit\Framework\TestCase +class ConfigurableTest extends TestCase { /** * @var StoreManagerInterface @@ -28,26 +35,36 @@ class ConfigurableTest extends \PHPUnit\Framework\TestCase */ private $productRepository; - protected function setUp() + /** + * @var StockItemRepositoryInterface + */ + private $stockRepository; + + /** + * @inheritdoc + */ + protected function setUp(): void { $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $this->stockRepository = Bootstrap::getObjectManager()->get(StockItemRepositoryInterface::class); } /** + * Test get product final price if one of child is disabled + * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @magentoDbIsolation disabled + * + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\StateException */ - public function testGetProductFinalPriceIfOneOfChildIsDisabled() + public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void { - /** @var Collection $collection */ - $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) - ->create(); - $configurableProduct = $collection - ->addIdFilter([1]) - ->addMinimalPrice() - ->load() - ->getFirstItem(); + $configurableProduct = $this->getConfigurableProductFromCollection(); $this->assertEquals(10, $configurableProduct->getMinimalPrice()); $childProduct = $this->productRepository->getById(10, false, null, true); @@ -58,34 +75,28 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled() $this->productRepository->save($childProduct); $this->storeManager->setCurrentStore($currentStoreId); - /** @var Collection $collection */ - $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) - ->create(); - $configurableProduct = $collection - ->addIdFilter([1]) - ->addMinimalPrice() - ->load() - ->getFirstItem(); + $configurableProduct = $this->getConfigurableProductFromCollection(); $this->assertEquals(20, $configurableProduct->getMinimalPrice()); } /** + * Test get product final price if one of child is disabled per store + * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @magentoDbIsolation disabled + * + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\StateException */ - public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore() + public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void { - /** @var Collection $collection */ - $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) - ->create(); - $configurableProduct = $collection - ->addIdFilter([1]) - ->addMinimalPrice() - ->load() - ->getFirstItem(); + $configurableProduct = $this->getConfigurableProductFromCollection(); $this->assertEquals(10, $configurableProduct->getMinimalPrice()); - $childProduct = $this->productRepository->getById(10, false, null, true); + $childProduct = $this->productRepository->get('simple_10', false, null, true); $childProduct->setStatus(Status::STATUS_DISABLED); // update in default store scope @@ -95,14 +106,53 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore() $this->productRepository->save($childProduct); $this->storeManager->setCurrentStore($currentStoreId); + $configurableProduct = $this->getConfigurableProductFromCollection(); + $this->assertEquals(20, $configurableProduct->getMinimalPrice()); + } + + /** + * Test get product minimal price if one child is out of stock + * + * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoDbIsolation disabled + * + * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testGetProductMinimalPriceIfOneOfChildIsOutOfStock(): void + { + $configurableProduct = $this->getConfigurableProductFromCollection(); + $this->assertEquals(10, $configurableProduct->getMinimalPrice()); + + $childProduct = $this->productRepository->getById(10, false, null, true); + $stockItem = $childProduct->getExtensionAttributes()->getStockItem(); + $stockItem->setIsInStock(Stock::STOCK_OUT_OF_STOCK); + $this->stockRepository->save($stockItem); + + $configurableProduct = $this->getConfigurableProductFromCollection(); + $this->assertEquals(20, $configurableProduct->getMinimalPrice()); + } + + /** + * Retrieve configurable product. + * Returns Configurable product that was created by Magento/ConfigurableProduct/_files/product_configurable.php + * fixture + * + * @return ProductInterface + */ + private function getConfigurableProductFromCollection(): ProductInterface + { /** @var Collection $collection */ $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) ->create(); + /** @var ProductInterface $configurableProduct */ $configurableProduct = $collection ->addIdFilter([1]) ->addMinimalPrice() ->load() ->getFirstItem(); - $this->assertEquals(20, $configurableProduct->getMinimalPrice()); + + return $configurableProduct; } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php index 4c31fb740c57e..1ef7d54c5aa78 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php @@ -65,6 +65,7 @@ public function testGetAddressEditUrl() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider hasPrimaryAddressDataProvider + * @magentoAppIsolation enabled */ public function testHasPrimaryAddress($customerId, $expected) { @@ -82,6 +83,7 @@ public function hasPrimaryAddressDataProvider() /** * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoAppIsolation enabled */ public function testGetAdditionalAddresses() { @@ -98,6 +100,7 @@ public function testGetAdditionalAddresses() /** * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getAdditionalAddressesDataProvider + * @magentoAppIsolation enabled */ public function testGetAdditionalAddressesNegative($customerId, $expected) { @@ -115,6 +118,7 @@ public function getAdditionalAddressesDataProvider() /** * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @magentoAppIsolation enabled */ public function testGetAddressHtml() { @@ -134,6 +138,7 @@ public function testGetAddressHtmlWithoutAddress() /** * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled */ public function testGetCustomer() { @@ -158,6 +163,7 @@ public function testGetCustomerMissingCustomer() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getDefaultBillingDataProvider + * @magentoAppIsolation enabled */ public function testGetDefaultBilling($customerId, $expected) { @@ -175,6 +181,7 @@ public function getDefaultBillingDataProvider() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getDefaultShippingDataProvider + * @magentoAppIsolation enabled */ public function testGetDefaultShipping($customerId, $expected) { diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php new file mode 100644 index 0000000000000..ac11c6c08bd62 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Address; + +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Integration tests for the \Magento\Customer\Block\Address\Grid class + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\View\LayoutInterface + */ + private $layout; + + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer + */ + protected $currentCustomer; + + protected function setUp() + { + /** @var \PHPUnit_Framework_MockObject_MockObject $blockMock */ + $blockMock = $this->getMockBuilder( + \Magento\Framework\View\Element\BlockInterface::class + )->disableOriginalConstructor()->setMethods( + ['setTitle', 'toHtml'] + )->getMock(); + $blockMock->expects($this->any())->method('setTitle'); + + $this->currentCustomer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Customer\Helper\Session\CurrentCustomer::class); + $this->layout = Bootstrap::getObjectManager()->get(\Magento\Framework\View\LayoutInterface::class); + $this->layout->setBlock('head', $blockMock); + } + + protected function tearDown() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Customer\Model\CustomerRegistry $customerRegistry */ + $customerRegistry = $objectManager->get(\Magento\Customer\Model\CustomerRegistry::class); + // Cleanup customer from registry + $customerRegistry->remove(1); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled + */ + public function testGetAddressEditUrl() + { + $gridBlock = $this->createBlockForCustomer(1); + + $this->assertEquals( + 'http://localhost/index.php/customer/address/edit/id/1/', + $gridBlock->getAddressEditUrl(1) + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoAppIsolation enabled + */ + public function testGetAdditionalAddresses() + { + $gridBlock = $this->createBlockForCustomer(1); + $this->assertNotNull($gridBlock->getAdditionalAddresses()); + $this->assertCount(1, $gridBlock->getAdditionalAddresses()); + $this->assertInstanceOf( + \Magento\Customer\Api\Data\AddressInterface::class, + $gridBlock->getAdditionalAddresses()[0] + ); + $this->assertEquals(2, $gridBlock->getAdditionalAddresses()[0]->getId()); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @dataProvider getAdditionalAddressesDataProvider + * @magentoAppIsolation enabled + */ + public function testGetAdditionalAddressesNegative($customerId, $expected) + { + $gridBlock = $this->createBlockForCustomer($customerId); + $this->currentCustomer->setCustomerId($customerId); + $this->assertEquals($expected, $gridBlock->getAdditionalAddresses()); + } + + public function getAdditionalAddressesDataProvider() + { + return ['5' => [5, []]]; + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @magentoAppIsolation enabled + */ + public function testGetAddressHtmlWithoutAddress() + { + $gridBlock = $this->createBlockForCustomer(5); + $this->assertEquals('', $gridBlock->getAddressHtml(null)); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled + */ + public function testGetCustomer() + { + $gridBlock = $this->createBlockForCustomer(1); + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = Bootstrap::getObjectManager()->get( + \Magento\Customer\Api\CustomerRepositoryInterface::class + ); + $customer = $customerRepository->getById(1); + $object = $gridBlock->getCustomer(); + $this->assertEquals($customer, $object); + } + + /** + * Create address book block for customer + * + * @param int $customerId + * @return \Magento\Framework\View\Element\BlockInterface + */ + private function createBlockForCustomer($customerId) + { + $this->currentCustomer->setCustomerId($customerId); + return $this->layout->createBlock( + \Magento\Customer\Block\Address\Grid::class, + '', + ['currentCustomer' => $this->currentCustomer] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Address/SaveTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Address/SaveTest.php new file mode 100644 index 0000000000000..cbfa794f94dc0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Address/SaveTest.php @@ -0,0 +1,195 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Controller\Adminhtml\Address; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; + +/** + * @magentoAppArea adminhtml + */ +class SaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /** @var AccountManagementInterface */ + private $accountManagement; + + /** @var \Magento\TestFramework\ObjectManager */ + private $objectManager; + + /** @var \Magento\Customer\Controller\Adminhtml\Address\Save */ + private $customerAddress; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + $this->customerRepository = Bootstrap::getObjectManager()->get( + \Magento\Customer\Api\CustomerRepositoryInterface::class + ); + $this->accountManagement = Bootstrap::getObjectManager()->get( + \Magento\Customer\Api\AccountManagementInterface::class + ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->customerAddress = $this->objectManager->get(\Magento\Customer\Controller\Adminhtml\Address\Save::class); + } + + /** + * @inheritDoc + */ + protected function tearDown() + { + /** + * Unset customer data + */ + Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + + /** + * Unset messages + */ + Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * + * Check that customer id set and addresses saved + */ + public function testSaveActionWithValidAddressData() + { + $customer = $this->customerRepository->get('customer5@example.com'); + $customerId = $customer->getId(); + $post = [ + 'parent_id' => $customerId, + 'firstname' => 'test firstname', + 'lastname' => 'test lastname', + 'street' => ['test street'], + 'city' => 'test city', + 'region_id' => 10, + 'country_id' => 'US', + 'postcode' => '01001', + 'telephone' => '+7000000001', + ]; + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); + + $this->objectManager->get(\Magento\Backend\Model\Session::class)->setCustomerFormData($post); + + $this->customerAddress->execute(); + + $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); + + /** Check that customer data were cleaned after it was saved successfully*/ + $this->assertEmpty($this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerData()); + + $customer = $this->customerRepository->getById($customerId); + + $this->assertEquals('Firstname', $customer->getFirstname()); + $addresses = $customer->getAddresses(); + $this->assertCount(1, $addresses); + $this->assertNull($this->accountManagement->getDefaultBillingAddress($customerId)); + $this->assertNull($this->accountManagement->getDefaultShippingAddress($customerId)); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * + * Check that customer id set and addresses saved + */ + public function testSaveActionWithDefaultShippingAndBilling() + { + $customer = $this->customerRepository->get('customer5@example.com'); + $customerId = $customer->getId(); + $post = [ + 'parent_id' => $customerId, + 'firstname' => 'test firstname', + 'lastname' => 'test lastname', + 'street' => ['test street'], + 'city' => 'test city', + 'region_id' => 10, + 'country_id' => 'US', + 'postcode' => '01001', + 'telephone' => '+7000000001', + 'default_billing' => true, + 'default_shipping' => true + ]; + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); + + $this->objectManager->get(\Magento\Backend\Model\Session::class)->setCustomerFormData($post); + + $this->customerAddress->execute(); + /** + * Check that errors was generated and set to session + */ + $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); + + /** + * Check that customer data were cleaned after it was saved successfully + */ + $this->assertEmpty($this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerData()); + + /** + * Remove stored customer from registry + */ + $this->objectManager->get(\Magento\Customer\Model\CustomerRegistry::class)->remove($customerId); + $customer = $this->customerRepository->get('customer5@example.com'); + $this->assertEquals('Firstname', $customer->getFirstname()); + $addresses = $customer->getAddresses(); + $this->assertCount(1, $addresses); + + $this->assertNotNull($this->accountManagement->getDefaultBillingAddress($customerId)); + $this->assertNotNull($this->accountManagement->getDefaultShippingAddress($customerId)); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * + * Check that customer id set and addresses saved + */ + public function testSaveActionWithExistingAdresses() + { + $customer = $this->customerRepository->get('customer@example.com'); + $customerId = $customer->getId(); + $post = [ + 'parent_id' => $customerId, + 'firstname' => 'test firstname', + 'lastname' => 'test lastname', + 'street' => ['test street'], + 'city' => 'test city', + 'region_id' => 10, + 'country_id' => 'US', + 'postcode' => '01001', + 'telephone' => '+7000000001', + ]; + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); + + $this->objectManager->get(\Magento\Backend\Model\Session::class)->setCustomerFormData($post); + + $this->customerAddress->execute(); + /** + * Check that errors was generated and set to session + */ + $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); + + /** + * Check that customer data were cleaned after it was saved successfully + */ + $this->assertEmpty($this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerData()); + + $customer = $this->customerRepository->getById($customerId); + + $this->assertEquals('test firstname', $customer->getFirstname()); + $addresses = $customer->getAddresses(); + $this->assertCount(4, $addresses); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 2fe49efd74a6d..292d61c392d06 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -122,226 +122,6 @@ public function testSaveActionWithInvalidFormData() $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new')); } - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithInvalidCustomerAddressData() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 0, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'example@domain.com', - 'default_billing' => '_item1', - ], - 'address' => ['_item1' => []], - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /** - * Check that errors was generated and set to session - */ - $this->assertSessionMessages( - $this->logicalNot($this->isEmpty()), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - /** - * Check that customer data were set to session - */ - $this->assertArraySubset( - $post, - $this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() - ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new')); - } - - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithValidCustomerDataAndValidAddressData() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 0, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'example@domain.com', - 'default_billing' => '_item1', - 'password' => 'password', - ], - 'address' => [ - '_item1' => [ - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'street' => ['test street'], - 'city' => 'test city', - 'region_id' => 10, - 'country_id' => 'US', - 'postcode' => '01001', - 'telephone' => '+7000000001', - 'default_billing' => 'true', - ], - ], - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('back', '1'); - - // Emulate setting customer data to session in editAction - $this->objectManager->get(\Magento\Backend\Model\Session::class)->setCustomerFormData($post); - - $this->dispatch('backend/customer/index/save'); - /** - * Check that errors was generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - /** - * Check that customer data were cleaned after it was saved successfully - */ - $this->assertEmpty($this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerData()); - - /** - * Check that success message is set - */ - $this->assertSessionMessages( - $this->logicalNot($this->isEmpty()), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - - /** - * Check that customer id set and addresses saved - */ - $registry = $this->objectManager->get(\Magento\Framework\Registry::class); - $customerId = $registry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); - $customer = $this->customerRepository->getById($customerId); - $this->assertEquals('test firstname', $customer->getFirstname()); - $addresses = $customer->getAddresses(); - $this->assertEquals(1, count($addresses)); - $this->assertNotEquals(0, $this->accountManagement->getDefaultBillingAddress($customerId)); - $this->assertNull($this->accountManagement->getDefaultShippingAddress($customerId)); - - $urlPatternParts = [ - $this->_baseControllerUrl . 'edit', - 'id/' . $customerId, - 'back/1', - ]; - $urlPattern = '/^' . str_replace('/', '\/', implode('(/.*/)|/', $urlPatternParts)) . '/'; - - $this->assertRedirect( - $this->matchesRegularExpression($urlPattern) - ); - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertEmpty($subscriber->getId()); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionExistingCustomerAndExistingAddressData() - { - $post = [ - 'customer' => [ - 'entity_id' => '1', - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - 'created_at' => '2000-01-01 00:00:00', - 'default_shipping' => '_item1', - 'default_billing' => 1, - ], - 'address' => [ - '1' => [ - 'firstname' => 'update firstname', - 'lastname' => 'update lastname', - 'street' => ['update street'], - 'city' => 'update city', - 'region_id' => 10, - 'country_id' => 'US', - 'postcode' => '01001', - 'telephone' => '+7000000001', - 'default_billing' => 'true', - ], - '_item1' => [ - 'firstname' => 'new firstname', - 'lastname' => 'new lastname', - 'street' => ['new street'], - 'city' => 'new city', - 'region_id' => 10, - 'country_id' => 'US', - 'postcode' => '01001', - 'telephone' => '+7000000001', - 'default_shipping' => 'true', - ], - '_template_' => [ - 'firstname' => '', - 'lastname' => '', - 'street' => [], - 'city' => '', - 'region_id' => 10, - 'country_id' => 'US', - 'postcode' => '', - 'telephone' => '', - ], - ], - 'subscription' => '', - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/save'); - - /** Check that success message is set */ - $this->assertSessionMessages( - $this->equalTo(['You saved the customer.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - - /** Check that customer id set and addresses saved */ - $registry = $this->objectManager->get(\Magento\Framework\Registry::class); - $customerId = $registry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); - $customer = $this->customerRepository->getById($customerId); - $this->assertEquals('test firstname', $customer->getFirstname()); - - /** - * Addresses should be removed by - * \Magento\Customer\Model\ResourceModel\Customer::_saveAddresses during _afterSave - * addressOne - updated - * addressTwo - removed - * addressThree - removed - * _item1 - new address - */ - $addresses = $customer->getAddresses(); - $this->assertEquals(2, count($addresses)); - $updatedAddress = $this->addressRepository->getById(1); - $this->assertEquals('update firstname', $updatedAddress->getFirstname()); - $this->assertTrue($updatedAddress->isDefaultBilling()); - $this->assertEquals($updatedAddress->getId(), $customer->getDefaultBilling()); - $newAddress = $this->accountManagement->getDefaultShippingAddress($customerId); - $this->assertEquals('new firstname', $newAddress->getFirstname()); - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertNotEmpty($subscriber->getId()); - $this->assertEquals(1, $subscriber->getStatus()); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); - } - /** * @magentoDataFixture Magento/Newsletter/_files/subscribers.php */ @@ -544,7 +324,7 @@ public function testNewAction() /** * Test the editing of a new customer that has not been saved but the page has been reloaded */ - public function testNewActionWithCustomerData() + public function te1stNewActionWithCustomerData() { $customerData = [ 'customer_id' => 0, @@ -679,59 +459,6 @@ public function testValidateCustomerWithAddressSuccess() $this->assertEquals('{"error":0}', $body); } - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/customer_address.php - */ - public function testValidateCustomerWithAddressFailure() - { - $customerData = [ - 'customer' => [ - 'entity_id' => '1', - 'middlename' => 'new middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => '', - 'lastname' => '', - 'email' => '*', - 'default_shipping' => '_item1', - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - ], - 'address' => [ - '1' => [ - 'firstname' => '', - 'lastname' => '', - 'street' => ['update street'], - 'city' => 'update city', - 'postcode' => '01001', - 'telephone' => '', - ], - '_template_' => [ - 'lastname' => '', - 'street' => [], - 'city' => '', - 'country_id' => 'US', - 'postcode' => '', - 'telephone' => '', - ], - ], - ]; - /** - * set customer data - */ - $this->getRequest()->setPostValue($customerData)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/validate'); - $body = $this->getResponse()->getBody(); - - $this->assertContains('{"error":true,"messages":', $body); - $this->assertContains('\"First Name\" is a required value', $body); - $this->assertContains('\"Last Name\" is a required value.', $body); - $this->assertContains('\"Country\" is a required value.', $body); - $this->assertContains('\"Phone Number\" is a required value.', $body); - } - /** * @magentoDbIsolation enabled */ diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php index 4177698389850..381c580f55e60 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php @@ -17,6 +17,8 @@ use Magento\Store\Api\WebsiteRepositoryInterface; /** + * Class with integration tests for AddressRepository. + * * @SuppressWarnings(PHPMD.TooManyMethods) * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -38,6 +40,9 @@ class AddressRepositoryTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Api\DataObjectHelper */ private $dataObjectHelper; + /** + * Set up. + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -86,6 +91,9 @@ protected function setUp() $this->expectedAddresses = [$address, $address2]; } + /** + * Tear down. + */ protected function tearDown() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -95,6 +103,8 @@ protected function tearDown() } /** + * Test for save address changes. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -116,6 +126,8 @@ public function testSaveAddressChanges() } /** + * Test for method save address with new id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -130,6 +142,8 @@ public function testSaveAddressesIdSetButNotAlreadyExisting() } /** + * Test for method get address by id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -143,6 +157,8 @@ public function testGetAddressById() } /** + * Test for method get address by id with incorrect id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @expectedException \Magento\Framework\Exception\NoSuchEntityException * @expectedExceptionMessage No such entity with addressId = 12345 @@ -153,6 +169,8 @@ public function testGetAddressByIdBadAddressId() } /** + * Test for method save new address. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -179,6 +197,8 @@ public function testSaveNewAddress() } /** + * Test for method saaveNewAddress with new attributes. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -204,6 +224,8 @@ public function testSaveNewAddressWithAttributes() } /** + * Test for saving address with invalid address. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -227,6 +249,11 @@ public function testSaveNewInvalidAddress() } } + /** + * Test for saving address without existing customer. + * + * @return void + */ public function testSaveAddressesCustomerIdNotExist() { $proposedAddress = $this->_createSecondAddress()->setCustomerId(4200); @@ -238,6 +265,11 @@ public function testSaveAddressesCustomerIdNotExist() } } + /** + * Test for saving addresses with invalid customer id. + * + * @return void + */ public function testSaveAddressesCustomerIdInvalid() { $proposedAddress = $this->_createSecondAddress()->setCustomerId('this_is_not_a_valid_id'); @@ -250,6 +282,8 @@ public function testSaveAddressesCustomerIdInvalid() } /** + * Test for delete method. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php */ @@ -273,6 +307,8 @@ public function testDeleteAddress() } /** + * Test for deleteAddressById. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php */ @@ -296,6 +332,8 @@ public function testDeleteAddressById() } /** + * Test delete address from customer with incorrect address id. + * * @magentoDataFixture Magento/Customer/_files/customer.php */ public function testDeleteAddressFromCustomerBadAddressId() @@ -309,10 +347,13 @@ public function testDeleteAddressFromCustomerBadAddressId() } /** + * Test for searching addressed. + * * @param \Magento\Framework\Api\Filter[] $filters * @param \Magento\Framework\Api\Filter[] $filterGroup * @param \Magento\Framework\Api\SortOrder[] $filterOrders * @param array $expectedResult array of expected results indexed by ID + * @param int $currentPage current page for search criteria * * @dataProvider searchAddressDataProvider * @@ -320,7 +361,7 @@ public function testDeleteAddressFromCustomerBadAddressId() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled */ - public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expectedResult) + public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expectedResult, $currentPage) { /** @var \Magento\Framework\Api\SearchCriteriaBuilder $searchBuilder */ $searchBuilder = $this->objectManager->create(\Magento\Framework\Api\SearchCriteriaBuilder::class); @@ -337,7 +378,7 @@ public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expe } $searchBuilder->setPageSize(1); - $searchBuilder->setCurrentPage(2); + $searchBuilder->setCurrentPage($currentPage); $searchCriteria = $searchBuilder->create(); $searchResults = $this->repository->getList($searchCriteria); @@ -355,6 +396,11 @@ public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expe $this->assertEquals($expectedResult[$expectedResultIndex]['firstname'], $items[0]->getFirstname()); } + /** + * Data provider for searchAddresses. + * + * @return array + */ public function searchAddressDataProvider() { /** @@ -375,6 +421,7 @@ public function searchAddressDataProvider() [ ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 1 ], 'Address with city CityM' => [ [$filterBuilder->setField('city')->setValue('CityM')->create()], @@ -383,6 +430,7 @@ public function searchAddressDataProvider() [ ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 1 ], 'Addresses with firstname John sorted by firstname desc, city asc' => [ [$filterBuilder->setField('firstname')->setValue('John')->create()], @@ -395,6 +443,7 @@ public function searchAddressDataProvider() ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ], + 2 ], 'Addresses with postcode of either 75477 or 47676 sorted by city desc' => [ [], @@ -409,6 +458,7 @@ public function searchAddressDataProvider() ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 2 ], 'Addresses with postcode greater than 0 sorted by firstname asc, postcode desc' => [ [$filterBuilder->setField('postcode')->setValue('0')->setConditionType('gt')->create()], @@ -421,11 +471,14 @@ public function searchAddressDataProvider() ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 2 ], ]; } /** + * Test for save addresses with restricted countries. + * * @magentoDataFixture Magento/Customer/Fixtures/customer_sec_website.php */ public function testSaveAddressWithRestrictedCountries() diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_two_addresses_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_two_addresses_rollback.php new file mode 100644 index 0000000000000..414d31ac9d0ac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_two_addresses_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var AddressRepositoryInterface $addressRepository */ +$addressRepository = Bootstrap::getObjectManager()->get(AddressRepositoryInterface::class); + +foreach ([1, 2] as $addressId) { + try { + $addressRepository->deleteById($addressId); + } catch (NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses.php new file mode 100644 index 0000000000000..0948e46173615 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Model\Customer; +use Magento\Customer\Model\CustomerRegistry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); +/** @var Customer $customer */ +$customer = Bootstrap::getObjectManager()->get(Customer::class); + +$customer->setWebsiteId(1) + ->setId(1) + ->setEmail('customer@example.com') + ->setPassword('password') + ->setGroupId(1) + ->setStoreId(1) + ->setIsActive(1) + ->setPrefix('Mr.') + ->setFirstname('John') + ->setMiddlename('A') + ->setLastname('Smith') + ->setSuffix('Esq.') + ->setGender(0) + ->save(); +$customerRegistry->remove($customer->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses_rollback.php new file mode 100644 index 0000000000000..d5f9308ee77b7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_without_addresses_rollback.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $customerRepository->deleteById(1); +} catch (NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} +$customerRegistry->remove(1); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock.php new file mode 100644 index 0000000000000..6ef9b5cd5b0a8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\GroupedProduct\Model\Product\Type\Grouped; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); + +$productLinkFactory = Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\Data\ProductLinkInterfaceFactory::class); +$productConfigs = [ + [ + 'id' => '100000001', + 'stock_config' => ['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1] + ], + [ + 'id' => '100000002', + 'stock_config' => ['use_config_manage_stock' => 1, 'qty' => 0, 'is_qty_decimal' => 0, 'is_in_stock' => 0] + ] +]; + +foreach ($productConfigs as $productConfig) { + /** @var $product Product */ + $product = Bootstrap::getObjectManager()->create(Product::class); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setId($productConfig['id']) + ->setWebsiteIds([1]) + ->setAttributeSetId(4) + ->setName('Simple ' . $productConfig['id']) + ->setSku('simple_' . $productConfig['id']) + ->setPrice(100) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData($productConfig['stock_config']); + + $linkedProducts[] = $productRepository->save($product); +} + +/** @var $product Product */ +$product = Bootstrap::getObjectManager()->create(Product::class); + +$product->setTypeId(Grouped::TYPE_CODE) + ->setId('100000003') + ->setWebsiteIds([1]) + ->setAttributeSetId(4) + ->setName('Grouped Product') + ->setSku('grouped') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); + +foreach ($linkedProducts as $linkedProduct) { + /** @var \Magento\Catalog\Api\Data\ProductLinkInterface $productLink */ + $productLink = $productLinkFactory->create(); + $productLink->setSku($product->getSku()) + ->setLinkType('associated') + ->setLinkedProductSku($linkedProduct->getSku()) + ->setLinkedProductType($linkedProduct->getTypeId()) + ->getExtensionAttributes() + ->setQty(1); + $newLinks[] = $productLink; +} + +$product->setProductLinks($newLinks); + +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock_rollback.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock_rollback.php new file mode 100644 index 0000000000000..b81c008cf1ab6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock_rollback.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$skuList = ['simple_100000001', 'simple_100000002', 'grouped']; +foreach ($skuList as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + + $stockStatus = $objectManager->create(\Magento\CatalogInventory\Model\Stock\Status::class); + $stockStatus->load($product->getData('entity_id'), 'product_id'); + $stockStatus->delete(); + + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/ImportTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/ImportTest.php index 5ba955430021f..93fa04806d577 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Model/ImportTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/ImportTest.php @@ -5,6 +5,7 @@ */ namespace Magento\ImportExport\Model; +use Magento\Framework\Phrase; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; /** @@ -33,9 +34,7 @@ class ImportTest extends \PHPUnit\Framework\TestCase 'catalog_product' => [ 'token' => \Magento\ImportExport\Model\Source\Import\Behavior\Basic::class, 'code' => 'basic_behavior', - 'notes' => [ - \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE => "Note: Product IDs will be regenerated." - ], + 'notes' => [], ], 'customer_composite' => [ 'token' => \Magento\ImportExport\Model\Source\Import\Behavior\Basic::class, @@ -70,7 +69,7 @@ protected function setUp() \Magento\ImportExport\Model\Import\Config::class ); $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\ImportExport\Model\Import::class, + Import::class, ['importConfig' => $this->_importConfig] ); } @@ -108,8 +107,8 @@ public function testValidateSource() $validationStrategy = ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_STOP_ON_ERROR; $this->_model->setEntity('catalog_product'); - $this->_model->setData(\Magento\ImportExport\Model\Import::FIELD_NAME_VALIDATION_STRATEGY, $validationStrategy); - $this->_model->setData(\Magento\ImportExport\Model\Import::FIELD_NAME_ALLOWED_ERROR_COUNT, 0); + $this->_model->setData(Import::FIELD_NAME_VALIDATION_STRATEGY, $validationStrategy); + $this->_model->setData(Import::FIELD_NAME_ALLOWED_ERROR_COUNT, 0); /** @var \Magento\ImportExport\Model\Import\AbstractSource|\PHPUnit_Framework_MockObject_MockObject $source */ $source = $this->getMockForAbstractClass( @@ -159,6 +158,8 @@ public function testGetEntityEntityIsNotSet() */ public function testGetEntityBehaviors() { + $this->prepareProductNotes(); + $importModel = $this->_model; $actualBehaviors = $importModel->getEntityBehaviors(); @@ -200,4 +201,24 @@ public function testGetUniqueEntityBehaviors() $this->assertEquals($behaviorClass, $actualBehaviors[$behaviorCode]); } } + + /** + * Add Catalog Product Notes to expected results. + * + * @return void + * @ SuppressWarnings(PHPMD.) + */ + private function prepareProductNotes(): void + { + $this->_entityBehaviors['catalog_product']['notes'] = + [ + Import::BEHAVIOR_APPEND => new Phrase('New product data is added to the existing product data for' + . ' the existing entries in the database. All fields except sku can be updated.'), + Import::BEHAVIOR_REPLACE => new Phrase('The existing product data is replaced with new data.' + . ' <b>Exercise caution when replacing data because the existing product data will be completely' + . ' cleared and all references in the system will be lost.</b>'), + Import::BEHAVIOR_DELETE => new Phrase('Any entities in the import data that already exist in the' + . ' database are deleted from the database.'), + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php b/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php index fba5354b691d6..2e447e7854a7c 100644 --- a/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php +++ b/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php @@ -56,8 +56,8 @@ public function testToHtml() $this->_customerSession->loginById(1); $translation = __('Not you?'); - $this->assertStringMatchesFormat( - '%A<span>%A<a%Ahref="' . $this->_block->getHref() . '"%A>' . $translation . '</a>%A</span>%A', + $this->assertContains( + '<a href="' . $this->_block->getHref() . '">' . $translation . '</a>', $this->_block->toHtml() ); $this->_customerSession->logout(); diff --git a/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php b/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php index ecf2cd77a13ff..d0c253fc7a64b 100644 --- a/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php @@ -9,7 +9,6 @@ use Magento\Customer\Model\Context; /** - * @magentoDataFixture Magento/Persistent/_files/persistent.php * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ObserverTest extends \PHPUnit\Framework\TestCase @@ -29,11 +28,6 @@ class ObserverTest extends \PHPUnit\Framework\TestCase */ protected $customerRepository; - /** - * @var \Magento\Persistent\Helper\Session - */ - protected $_persistentSessionHelper; - /** * @var \Magento\Framework\ObjectManagerInterface */ @@ -44,11 +38,6 @@ class ObserverTest extends \PHPUnit\Framework\TestCase */ protected $_observer; - /** - * @var \Magento\Customer\Model\Session - */ - protected $_customerSession; - /** * @var \Magento\Checkout\Model\Session | \PHPUnit_Framework_MockObject_MockObject */ @@ -58,8 +47,6 @@ public function setUp() { $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->_customerSession = $this->_objectManager->get(\Magento\Customer\Model\Session::class); - $this->_customerViewHelper = $this->_objectManager->create( \Magento\Customer\Helper\View::class ); @@ -75,8 +62,6 @@ public function setUp() \Magento\Checkout\Model\Session::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $this->_persistentSessionHelper = $this->_objectManager->create(\Magento\Persistent\Helper\Session::class); - $this->_observer = $this->_objectManager->create( \Magento\Persistent\Model\Observer::class, [ @@ -89,16 +74,11 @@ public function setUp() } /** - * @magentoConfigFixture current_store persistent/options/enabled 1 - * @magentoConfigFixture current_store persistent/options/remember_enabled 1 - * @magentoConfigFixture current_store persistent/options/remember_default 1 * @magentoAppArea frontend * @magentoAppIsolation enabled */ public function testEmulateWelcomeBlock() { - $this->_customerSession->loginById(1); - $httpContext = new \Magento\Framework\App\Http\Context(); $httpContext->setValue(Context::CONTEXT_AUTH, 1, 1); $block = $this->_objectManager->create( @@ -108,15 +88,7 @@ public function testEmulateWelcomeBlock() ] ); $this->_observer->emulateWelcomeBlock($block); - $customerName = $this->_escaper->escapeHtml( - $this->_customerViewHelper->getCustomerName( - $this->customerRepository->getById( - $this->_persistentSessionHelper->getSession()->getCustomerId() - ) - ) - ); - $translation = __('Welcome, %1!', $customerName)->__toString(); - $this->assertStringMatchesFormat('%A' . $translation . '%A', $block->getWelcome()->__toString()); - $this->_customerSession->logout(); + + $this->assertEquals(' ', $block->getWelcome()); } } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/Item/UpdaterTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/Item/UpdaterTest.php new file mode 100644 index 0000000000000..a375b3c665f2a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/Item/UpdaterTest.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\Quote\Item; + +use Magento\Quote\Api\CartItemRepositoryInterface; +use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Quote\Model\Quote; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Tests \Magento\Quote\Model\Quote\Item\Updater + */ +class UpdaterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Updater + */ + private $updater; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->updater = $this->objectManager->create(Updater::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/quote_with_custom_price.php + * @return void + */ + public function testUpdate(): void + { + /** @var CartItemRepositoryInterface $quoteItemRepository */ + $quoteItemRepository = $this->objectManager->create(CartItemRepositoryInterface::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); + $quoteId = $quote->load('test01', 'reserved_order_id')->getId(); + /** @var CartItemInterface[] $quoteItems */ + $quoteItems = $quoteItemRepository->getList($quoteId); + /** @var CartItemInterface $actualQuoteItem */ + $actualQuoteItem = array_pop($quoteItems); + $this->assertInstanceOf(CartItemInterface::class, $actualQuoteItem); + + $this->updater->update($actualQuoteItem, ['qty' => 1]); + + $this->assertNull( + $actualQuoteItem->getCustomPrice(), + 'Item custom price has to be null' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/Controller/CaseCheckAddingProductReviewTest.php b/dev/tests/integration/testsuite/Magento/Review/Controller/CaseCheckAddingProductReviewTest.php new file mode 100644 index 0000000000000..49e980ed53602 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/Controller/CaseCheckAddingProductReviewTest.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Review\Controller; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Request; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test review product controller behavior + * + * @magentoAppArea frontend + */ +class CaseCheckAddingProductReviewTest extends AbstractController +{ + /** + * Test adding a review for allowed guests with incomplete data by a not logged in user + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Review/_files/config.php + * @magentoDataFixture Magento/Catalog/_files/products.php + */ + public function testAttemptForGuestToAddReviewsWithIncompleteData() + { + $product = $this->getProduct(); + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $post = [ + 'nickname' => 'Test nick', + 'title' => 'Summary', + 'form_key' => $formKey->getFormKey(), + ]; + $this->prepareRequestData($post); + $this->dispatch('review/product/post/id/' . $product->getId()); + $this->assertSessionMessages( + $this->equalTo(['Please enter a review.']), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Test adding a review for not allowed guests by a guest + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Review/_files/disable_config.php + * @magentoDataFixture Magento/Catalog/_files/products.php + */ + public function testAttemptForGuestToAddReview() + { + $product = $this->getProduct(); + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $post = [ + 'nickname' => 'Test nick', + 'title' => 'Summary', + 'detail' => 'Test Details', + 'form_key' => $formKey->getFormKey(), + ]; + + $this->prepareRequestData($post); + $this->dispatch('review/product/post/id/' . $product->getId()); + + $this->assertRedirect($this->stringContains('customer/account/login')); + } + + /** + * Test successfully adding a product review by a guest + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Review/_files/config.php + * @magentoDataFixture Magento/Catalog/_files/products.php + */ + public function testSuccessfullyAddingProductReviewForGuest() + { + $product = $this->getProduct(); + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $post = [ + 'nickname' => 'Test nick', + 'title' => 'Summary', + 'detail' => 'Test Details', + 'form_key' => $formKey->getFormKey(), + ]; + + $this->prepareRequestData($post); + $this->dispatch('review/product/post/id/' . $product->getId()); + + $this->assertSessionMessages( + $this->equalTo(['You submitted your review for moderation.']), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * @return ProductInterface + */ + private function getProduct() + { + return $this->_objectManager->get(ProductRepositoryInterface::class)->get('custom-design-simple-product'); + } + + /** + * @param array $postData + * @return void + */ + private function prepareRequestData($postData) + { + $this->getRequest()->setMethod(Request::METHOD_POST); + $this->getRequest()->setPostValue($postData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/disable_config.php b/dev/tests/integration/testsuite/Magento/Review/_files/disable_config.php new file mode 100644 index 0000000000000..ee21150bd6129 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/disable_config.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var Value $config */ +use Magento\Framework\App\Config\Value; + +$config = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(Value::class); +$config->setPath('catalog/review/allow_guest'); +$config->setScope('default'); +$config->setScopeId(0); +$config->setValue(0); +$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php index 251a384580062..1f4253f18487c 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php @@ -7,6 +7,7 @@ use Magento\Sales\Model\Order; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Sales\Model\Order\Payment; require 'order.php'; /** @var Order $order */ @@ -68,13 +69,26 @@ $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); + /** @var Payment $payment */ + $payment = $objectManager->create(Payment::class); + $payment->setMethod('checkmo') + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation( + 'metadata', + [ + 'type' => 'free', + 'fraudulent' => false, + ] + ); + $order ->setData($orderData) ->addItem($orderItem) ->setCustomerIsGuest(true) ->setCustomerEmail('customer@null.com') ->setBillingAddress($billingAddress) - ->setShippingAddress($shippingAddress); + ->setShippingAddress($shippingAddress) + ->setPayment($payment); $orderRepository->save($order); $orderList[] = $order; diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php new file mode 100644 index 0000000000000..29a7aa4d90334 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Sales\Model\Order\Payment; +use Magento\Store\Model\StoreManagerInterface; + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; +/** @var \Magento\Catalog\Model\Product $product */ + +$addressData = include __DIR__ . '/address_data.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$billingAddress = $objectManager->create(OrderAddress::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +/** @var Payment $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod('checkmo') + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation( + 'metadata', + [ + 'type' => 'free', + 'fraudulent' => false, + ] + ); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setDiscountAmount(2) + ->setBaseRowTotal($product->getPrice()) + ->setBaseDiscountAmount(2) + ->setTaxAmount(1) + ->setBaseTaxAmount(1); + +/** @var Order $order */ +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(100) + ->setGrandTotal(100) + ->setBaseSubtotal(100) + ->setBaseGrandTotal(100) + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()) + ->addItem($orderItem) + ->setPayment($payment); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount_rollback.php new file mode 100644 index 0000000000000..1fb4b4636ab29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require 'default_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php index 4c892904b3c3e..61d8be98bdd22 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php @@ -44,5 +44,6 @@ $items[$orderItem->getId()] = $orderItem->getQtyOrdered(); } $shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); $transaction->addObject($invoice)->addObject($shipment)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php index 753adb1f38596..1a0a94b0ca951 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php @@ -33,6 +33,7 @@ 'grand_total' => 130.00, 'base_grand_total' => 130.00, 'subtotal' => 130.00, + 'total_paid' => 130.00, 'store_id' => 0, 'website_id' => 0, 'payment' => $payment @@ -55,6 +56,7 @@ 'grand_total' => 150.00, 'base_grand_total' => 150.00, 'subtotal' => 150.00, + 'total_paid' => 150.00, 'store_id' => 1, 'website_id' => 1, 'payment' => $payment @@ -66,6 +68,7 @@ 'grand_total' => 160.00, 'base_grand_total' => 160.00, 'subtotal' => 160.00, + 'total_paid' => 160.00, 'store_id' => 1, 'website_id' => 1, 'payment' => $payment @@ -89,6 +92,15 @@ $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); + /** @var Order\Item $orderItem */ + $orderItem = $objectManager->create(Order\Item::class); + $orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); + $order ->setData($orderData) ->addItem($orderItem) diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price.php new file mode 100644 index 0000000000000..97fb4f7a8fc32 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +include __DIR__ . '/quote.php'; + +$buyRequest = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Framework\DataObject::class, + [ + 'data' => [ + 'qty' => 1, + 'custom_price' => 12, + ], + ] +); +/** @var \Magento\Quote\Model\Quote $items */ +$items = $quote->getItemsCollection()->getItems(); +$quoteItem = reset($items); +$quote->updateItem($quoteItem->getId(), $buyRequest)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price_rollback.php new file mode 100644 index 0000000000000..2bcbb636b97af --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_custom_price_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +include __DIR__ . '/quote_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Setup/Declaration/WhitelistDeclarationTest.php b/dev/tests/integration/testsuite/Magento/Setup/Declaration/WhitelistDeclarationTest.php index 6b92e687284db..04f25f231c933 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Declaration/WhitelistDeclarationTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Declaration/WhitelistDeclarationTest.php @@ -47,7 +47,7 @@ public function setUp() /** * Checks that all declared table elements also declared into whitelist declaration. * - * @appIsolation + * @magentoAppIsolation enabled * @throws \Exception */ public function testConstraintsAndIndexesAreWhitelisted() diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php index 9690199eeb8b6..bb6d1687052e3 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php @@ -6,9 +6,12 @@ namespace Magento\Store\Model; +use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\Bootstrap; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\UrlInterface; +use Magento\Store\Api\StoreRepositoryInterface; use Zend\Stdlib\Parameters; /** @@ -267,12 +270,89 @@ public function testIsCanDelete() $this->assertFalse($this->model->isCanDelete()); } + /** + * @magentoDataFixture Magento/Store/_files/core_second_third_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDbIsolation disabled + */ public function testGetCurrentUrl() { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class) + ->setValue('web/url/use_store', true, ScopeInterface::SCOPE_STORE, 'secondstore'); + $this->model->load('admin'); - $this->model->expects($this->any())->method('getUrl')->will($this->returnValue('http://localhost/index.php')); + $this->model + ->expects($this->any())->method('getUrl') + ->will($this->returnValue('http://localhost/index.php')); $this->assertStringEndsWith('default', $this->model->getCurrentUrl()); $this->assertStringEndsNotWith('default', $this->model->getCurrentUrl(false)); + + /** @var \Magento\Store\Model\Store $secondStore */ + $secondStore = $objectManager->get(StoreRepositoryInterface::class)->get('secondstore'); + + /** @var \Magento\Catalog\Model\ProductRepository $productRepository */ + $productRepository = $objectManager->create(ProductRepository::class); + $product = $productRepository->get('simple'); + $product->setStoreId($secondStore->getId()); + $url = $product->getUrlInStore(); + + $this->assertEquals( + $secondStore->getBaseUrl().'catalog/product/view/id/1/s/simple-product/', + $url + ); + $this->assertEquals( + $secondStore->getBaseUrl().'?___from_store=default', + $secondStore->getCurrentUrl() + ); + $this->assertEquals( + $secondStore->getBaseUrl(), + $secondStore->getCurrentUrl(false) + ); + } + + /** + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDataFixture Magento/Catalog/_files/category_product.php + * @magentoDbIsolation disabled + */ + public function testGetCurrentUrlWithUseStoreInUrlFalse() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class) + ->setValue('web/url/use_store', false, ScopeInterface::SCOPE_STORE, 'default'); + + /** @var \Magento\Store\Model\Store $secondStore */ + $secondStore = $objectManager->get(StoreRepositoryInterface::class)->get('fixture_second_store'); + + /** @var \Magento\Catalog\Model\ProductRepository $productRepository */ + $productRepository = $objectManager->create(ProductRepository::class); + $product = $productRepository->get('simple333'); + + $product->setStoreId($secondStore->getId()); + $url = $product->getUrlInStore(); + + /** @var \Magento\Catalog\Model\CategoryRepository $categoryRepository */ + $categoryRepository = $objectManager->get(\Magento\Catalog\Model\CategoryRepository::class); + $category = $categoryRepository->get(333, $secondStore->getStoreId()); + + $this->assertEquals( + $secondStore->getBaseUrl().'catalog/category/view/s/category-1/id/333/', + $category->getUrl() + ); + $this->assertEquals( + $secondStore->getBaseUrl(). + 'catalog/product/view/id/333/s/simple-product-three/?___store=fixture_second_store', + $url + ); + $this->assertEquals( + $secondStore->getBaseUrl().'?___store=fixture_second_store&___from_store=default', + $secondStore->getCurrentUrl() + ); + $this->assertEquals( + $secondStore->getBaseUrl().'?___store=fixture_second_store', + $secondStore->getCurrentUrl(false) + ); } /** diff --git a/dev/tests/integration/testsuite/Magento/Test/Integrity/StaticFilesTest.php b/dev/tests/integration/testsuite/Magento/Test/Integrity/StaticFilesTest.php index 2afb6e90667dc..eee166ab341a9 100644 --- a/dev/tests/integration/testsuite/Magento/Test/Integrity/StaticFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/Test/Integrity/StaticFilesTest.php @@ -76,7 +76,7 @@ protected function setUp() /** * Scan references to files from other static files and assert they are correct * - * The CSS or LESS files may refer to other resources using @import or url() notation + * The CSS or LESS files may refer to other resources using `import` or url() notation * We want to check integrity of all these references * Note that the references may have syntax specific to the Magento preprocessing subsystem * diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index aa301e8d3e423..fe4067cdc49f5 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -69,6 +69,7 @@ public function testGetShipConfirmUrlLive() */ public function testCollectFreeRates() { + $this->markTestSkipped('Test is blocked by MAGETWO-97467.'); $rateRequest = Bootstrap::getObjectManager()->get(RateRequestFactory::class)->create(); $rateRequest->setDestCountryId('US'); $rateRequest->setDestRegionId('CA'); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js index ff420c4830476..9b9b1ce5b1614 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js @@ -12,7 +12,7 @@ require.config({ } }); -define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { +define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Squire, ko, $, registry) { 'use strict'; var injector = new Squire(), @@ -28,7 +28,17 @@ define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { 'Magento_Checkout/js/model/address-converter': jasmine.createSpy(), 'Magento_Checkout/js/model/quote': { isVirtual: jasmine.createSpy(), - shippingMethod: ko.observable() + shippingMethod: ko.observable(), + + /** + * Stub + */ + shippingAddress: function () { + + return { + 'countryId': 'AD' + }; + } }, 'Magento_Checkout/js/action/create-shipping-address': jasmine.createSpy().and.returnValue( jasmine.createSpyObj('newShippingAddress', ['getKey']) @@ -52,7 +62,7 @@ define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { 'checkoutData', ['setSelectedShippingAddress', 'setNewCustomerShippingAddress', 'setSelectedShippingRate'] ), - 'uiRegistry': jasmine.createSpy(), + 'Magento_Ui/js/lib/registry/registry': registry, 'Magento_Checkout/js/model/shipping-rate-service': jasmine.createSpy() }, obj; @@ -63,6 +73,7 @@ define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { obj = new Constr({ provider: 'provName', name: '', + parentName: 'test', index: '', popUpForm: { options: { @@ -156,12 +167,26 @@ define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { describe('"setShippingInformation" method', function () { it('Check method call.', function () { + spyOn(obj, 'validateShippingInformation').and.returnValue(false); expect(obj.setShippingInformation()).toBeUndefined(); }); }); describe('"validateShippingInformation" method', function () { it('Check method call on negative cases.', function () { + var country = { + 'indexedOptions': { + 'AD': + { + label: 'Andorra', + labeltitle: 'Andorra', + value: 'AD' + } + } + }; + + registry.set('test.shippingAddress.shipping-address-fieldset.country_id', country); + registry.set('checkout.errors', {}); obj.source = { get: jasmine.createSpy().and.returnValue(true), set: jasmine.createSpy(), diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js index ed2e29f4baf16..c98c459d98819 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js @@ -117,5 +117,64 @@ define([ expect(variation.source.data['configurable-matrix-serialized']).toEqual(resultMatrix); expect(variation.source.data['associated_product_ids_serialized']).toEqual(resultIds); }); + + it('checks that "unserializeData" unserializes data', function () { + var matrixString = '[{"name":"Small Product","attributes":"Size: small","price":5.5},' + + '{"name":"Medium Product","attributes":"Size: medium","price":10.99},' + + '{"name":"Large Product","attributes":"Size: large","price":25}]', + idString = '[100, 101, 102]', + resultMatrix = JSON.parse(matrixString), + resultIds = JSON.parse(idString); + + variation.source.data['configurable-matrix-serialized'] = matrixString; + variation.source.data['associated_product_ids_serialized'] = idString; + + variation.unserializeData(); + + expect(variation.source.data['configurable-matrix-serialized']).toBeUndefined(); + expect(variation.source.data['associated_product_ids_serialized']).toBeUndefined(); + expect(variation.source.data['configurable-matrix']).toEqual(resultMatrix); + expect(variation.source.data['associated_product_ids']).toEqual(resultIds); + }); + + it('checks that "serializeData" and "unserializeData" give proper result', function () { + var matrix = [ + { + name: 'Small Product', + attributes: 'Size: small', + price: 5.50 + }, + { + name: 'Medium Product', + attributes: 'Size: medium', + price: 10.99 + }, + { + name: 'Large Product', + attributes: 'Size: large', + price: 25 + } + ], + ids = [1, 2, 3], + resultMatrix = JSON.stringify(matrix), + resultIds = JSON.stringify(ids); + + variation.source.data['configurable-matrix'] = matrix; + variation.source.data['associated_product_ids'] = ids; + + variation.serializeData(); + + expect(variation.source.data['configurable-matrix']).toBeUndefined(); + expect(variation.source.data['associated_product_ids']).toBeUndefined(); + expect(variation.source.data['configurable-matrix-serialized']).toEqual(resultMatrix); + expect(variation.source.data['associated_product_ids_serialized']).toEqual(resultIds); + + variation.unserializeData(); + + expect(variation.source.data['configurable-matrix']).toEqual(matrix); + expect(variation.source.data['associated_product_ids']).toEqual(ids); + expect(variation.source.data['configurable-matrix-serialized']).toBeUndefined(); + expect(variation.source.data['associated_product_ids_serialized']).toBeUndefined(); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/adminhtml/js/view/form/components/form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/adminhtml/js/view/form/components/form.test.js new file mode 100644 index 0000000000000..2ef8736b4f0b8 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/adminhtml/js/view/form/components/form.test.js @@ -0,0 +1,71 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'uiRegistry', + 'Magento_Customer/js/form/components/form', + 'jquery' +], function (_, registry, Constr, $) { + 'use strict'; + + describe('Magento_Customer/js/form/components/form', function () { + + var obj, + originaljQueryAjax; + + beforeEach(function () { + originaljQueryAjax = $.ajax; + obj = new Constr({ + provider: 'provName', + name: '', + index: '' + }); + }); + + afterEach(function () { + $.ajax = originaljQueryAjax; + }); + + registry.set('provName', { + /** Stub */ + on: function () {}, + + /** Stub */ + get: function () {}, + + /** Stub */ + set: function () {} + }); + + describe('"deleteAddress" method', function () { + it('Check for defined ', function () { + expect(obj.hasOwnProperty('deleteAddress')).toBeDefined(); + }); + it('Check method type', function () { + var type = typeof obj.deleteAddress; + + expect(type).toEqual('function'); + }); + it('Check returned value if method called without arguments', function () { + expect(obj.deleteAddress()).toBeUndefined(); + }); + it('Check returned value type if method called without arguments', function () { + var type = typeof obj.deleteAddress(); + + expect(type).toEqual('undefined'); + }); + it('Should call not call ajax if arguments are empty', function () { + $.ajax = jasmine.createSpy(); + + spyOn(obj, 'deleteAddress'); + + expect(obj.deleteAddress()).toBeUndefined(); + + expect($.ajax).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/dev/tests/setup-integration/framework/tests/unit/phpunit.xml.dist b/dev/tests/setup-integration/framework/tests/unit/phpunit.xml.dist index b52ca37987cb7..1a93397caaa4a 100644 --- a/dev/tests/setup-integration/framework/tests/unit/phpunit.xml.dist +++ b/dev/tests/setup-integration/framework/tests/unit/phpunit.xml.dist @@ -21,4 +21,32 @@ <php> <ini name="date.timezone" value="America/Los_Angeles"/> </php> + <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="@magentoDbIsolation"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + </array> + </arguments> + </listener> + </listeners> </phpunit> diff --git a/dev/tests/setup-integration/phpunit.xml.dist b/dev/tests/setup-integration/phpunit.xml.dist index 7dd8609bdcadf..620ebbb5b8f0f 100644 --- a/dev/tests/setup-integration/phpunit.xml.dist +++ b/dev/tests/setup-integration/phpunit.xml.dist @@ -43,5 +43,61 @@ <listeners> <listener class="Magento\TestFramework\Event\PhpUnit"/> <listener class="Magento\TestFramework\ErrorLog\Listener"/> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppArea"> + <string>magentoAppArea</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoCache"> + <string>magentoCache</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="magentoDataFixture"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDataFixtureBeforeTransaction"> + <string>magentoDataFixtureBeforeTransaction</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + <element key="magentoIndexerDimensionMode"> + <string>magentoIndexerDimensionMode</string> + </element> + <element key="moduleName"> + <string>moduleName</string> + </element> + <element key="dataProviderFromFile"> + <string>dataProviderFromFile</string> + </element> + <element key="magentoSchemaFixture"> + <string>magentoSchemaFixture</string> + </element> + </array> + </arguments> + </listener> </listeners> </phpunit> diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php new file mode 100644 index 0000000000000..ee56158a54509 --- /dev/null +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CodeMessDetector\Rule\Design; + +use PDepend\Source\AST\ASTClass; +use PHPMD\AbstractNode; +use PHPMD\AbstractRule; +use PHPMD\Node\ClassNode; +use PHPMD\Rule\ClassAware; + +/** + * Session and Cookies must be used only in HTML Presentation layer. + */ +class CookieAndSessionMisuse extends AbstractRule implements ClassAware +{ + /** + * Is given class a controller? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isController(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\App\ActionInterface::class); + } + + /** + * Is given class a block? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isBlock(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class); + } + + /** + * Is given class an HTML UI data provider? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isUiDataProvider(\ReflectionClass $class): bool + { + return $class->isSubclassOf( + \Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface::class + ); + } + + /** + * Is given class an HTML UI Document? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isUiDocument(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\View\Element\UiComponent\DataProvider\Document::class) + || $class->getName() === \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class; + } + + /** + * Is given class a plugin for controllers? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isControllerPlugin(\ReflectionClass $class): bool + { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (preg_match('/^(after|around|before).+/i', $method->getName())) { + try { + $argument = $method->getParameters()[0]->getClass(); + } catch (\Throwable $exception) { + //Non-existing class (autogenerated perhaps) or doesn't have an argument. + continue; + } + if ($argument) { + $isAction = $argument->isSubclassOf(\Magento\Framework\App\ActionInterface::class) + || $argument->getName() === \Magento\Framework\App\ActionInterface::class; + if ($isAction) { + return true; + } + } + } + } + + return false; + } + + /** + * Is given class a plugin for blocks? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isBlockPlugin(\ReflectionClass $class): bool + { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (preg_match('/^(after|around|before).+/i', $method->getName())) { + try { + $argument = $method->getParameters()[0]->getClass(); + } catch (\Throwable $exception) { + //Non-existing class (autogenerated perhaps) or doesn't have an argument. + continue; + } + if ($argument) { + $isBlock = $argument->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class) + || $argument->getName() === \Magento\Framework\View\Element\BlockInterface::class; + if ($isBlock) { + return true; + } + } + } + } + + return false; + } + + /** + * Whether given class depends on classes to pay attention to. + * + * @param \ReflectionClass $class + * @return bool + */ + private function doesUseRestrictedClasses(\ReflectionClass $class): bool + { + $constructor = $class->getConstructor(); + if ($constructor) { + foreach ($constructor->getParameters() as $argument) { + try { + if ($class = $argument->getClass()) { + if ($class->isSubclassOf(\Magento\Framework\Session\SessionManagerInterface::class) + || $class->getName() === \Magento\Framework\Session\SessionManagerInterface::class + || $class->isSubclassOf(\Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class) + || $class->getName() === \Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class + ) { + return true; + } + } + } catch (\ReflectionException $exception) { + //Failed to load the argument's class information + continue; + } + } + } + + return false; + } + + /** + * @inheritdoc + * + * @param ClassNode|ASTClass $node + */ + public function apply(AbstractNode $node) + { + try { + $class = new \ReflectionClass($node->getFullQualifiedName()); + } catch (\Throwable $exception) { + //Failed to load class, nothing we can do + return; + } + + if ($this->doesUseRestrictedClasses($class)) { + if (!$this->isController($class) + && !$this->isBlock($class) + && !$this->isUiDataProvider($class) + && !$this->isUiDocument($class) + && !$this->isControllerPlugin($class) + && !$this->isBlockPlugin($class) + ) { + $this->addViolation($node, [$node->getFullQualifiedName()]); + } + } + } +} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php deleted file mode 100644 index 9ce891da718b4..0000000000000 --- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\CodeMessDetector\Rule\Design; - -use PHPMD\AbstractNode; -use PHPMD\AbstractRule; -use PHPMD\Node\ClassNode; -use PHPMD\Node\MethodNode; -use PDepend\Source\AST\ASTMethod; -use PHPMD\Rule\MethodAware; - -/** - * Detect direct request usages. - */ -class RequestAwareBlockMethod extends AbstractRule implements MethodAware -{ - /** - * @inheritDoc - * - * @param ASTMethod|MethodNode $method - */ - public function apply(AbstractNode $method) - { - $definedIn = $method->getParentType(); - try { - $isBlock = ($definedIn instanceof ClassNode) - && is_subclass_of( - $definedIn->getFullQualifiedName(), - \Magento\Framework\View\Element\AbstractBlock::class - ); - } catch (\Throwable $exception) { - //Failed to load classes. - return; - } - - if ($isBlock) { - $nodes = $method->findChildrenOfType('PropertyPostfix') + $method->findChildrenOfType('MethodPostfix'); - foreach ($nodes as $node) { - $name = mb_strtolower($node->getFirstChildOfType('Identifier')->getImage()); - if ($name === '_request' || $name === 'getrequest') { - $this->addViolation($method, [$method->getFullQualifiedName()]); - break; - } - } - } - } -} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml index 100e08276e6cf..73354c46d76b2 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml +++ b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml @@ -58,32 +58,30 @@ class PostOrder implements ActionInterface ]]> </example> </rule> - <rule name="RequestAwareBlockMethod" - class="Magento\CodeMessDetector\Rule\Design\RequestAwareBlockMethod" - message="{0} uses request object directly. Add user input validation and suppress this warning."> + <rule name="CookieAndSessionMisuse" + class="Magento\CodeMessDetector\Rule\Design\CookieAndSessionMisuse" + message= "The class {0} uses sessions or cookies while not being a part of HTML Presentation layer"> <description> <![CDATA[ -Blocks must not depend on being used with certain controllers. -If you use request object in a block directly you must validate all user input inside the block. +Sessions and cookies must only be used in classes directly responsible for HTML presentation because Web APIs do not +rely on cookies and sessions. If you need to get current user use Magento\Authorization\Model\UserContextInterface ]]> </description> <priority>2</priority> <properties /> <example> <![CDATA[ -class MyOrder extends AbstractBlock +class OrderProcessor { + public function __construct(SessionManagerInterface $session) { + $this->session = $session; + } - ....... - - public function getOrder() + public function place(OrderInterface $order) { - $orderId = $this->getRequest()->getParam('order_id'); - //Validate customer having such order. - if (!$this->hasOrder($this->getCustomerId(), $orderId)) { - ...deny access... - } - ..... + //Will not be present if processing a WebAPI request + $currentOrder = $this->session->get('current_order'); + ... } } ]]> diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php index 899a65c375321..49acd039a0960 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -168,8 +168,8 @@ private function validateLongDescriptionFormat( ) : void { $tokens = $phpcsFile->getTokens(); $longPtr = $phpcsFile->findNext($emptyTypeTokens, $shortPtrEnd + 1, $commentEndPtr - 1, true); - if (strtolower($tokens[$longPtr]['content']) === '{@inheritdoc}') { - $error = '{@inheritdoc} imports only short description, annotation must have long description'; + if (strtolower($tokens[$longPtr]['content']) === '@inheritdoc') { + $error = '@inheritdoc imports only short description, annotation must have long description'; $phpcsFile->addFixableError($error, $longPtr, 'MethodAnnotation'); } if ($longPtr !== false && $tokens[$longPtr]['code'] === T_DOC_COMMENT_STRING) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php b/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php index 9013f12b6b46b..5ce1ac333cc11 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php @@ -5,13 +5,16 @@ */ namespace Magento\Sniffs\Arrays; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; +/** + * Validate short array syntax is used. + */ class ShortArraySyntaxSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -19,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php b/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php index 694905cb37add..9d0d58950c4f8 100644 --- a/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php @@ -9,10 +9,13 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +/** + * Validate short echo syntax is used. + */ class ShortEchoSyntaxSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -20,7 +23,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php b/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php index 2abcf0531b009..528baeedf0476 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php @@ -20,7 +20,7 @@ class LineLengthSniff extends FilesLineLengthSniff protected $previousLineContent = ''; /** - * {@inheritdoc} + * @inheritdoc */ protected function checkLineLength($phpcsFile, $stackPtr, $lineContent) { diff --git a/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php b/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php index a3a49adf62a62..575b39542311a 100644 --- a/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php @@ -48,7 +48,7 @@ class LanguageConstructsSniff implements Sniff protected $directOutput = 'DirectOutput'; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -60,7 +60,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php index 49339054f8766..4f94e335e0363 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php @@ -13,7 +13,7 @@ * * Ensure that id selector is not used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#types + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#types */ class AvoidIdSniff implements Sniff { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php index 435eea118af63..61e2e69eeb86f 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php @@ -15,7 +15,7 @@ * Ensure there is a single blank line after the closing brace of a class definition * * @see Squiz_Sniffs_CSS_ClassDefinitionClosingBraceSpaceSniff - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#braces + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#braces */ class BracesFormattingSniff implements Sniff { @@ -27,7 +27,7 @@ class BracesFormattingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +35,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php index 798c13ff2699c..8c55edc13afc2 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php @@ -17,8 +17,7 @@ * - start with a letter (except helper classes); * - words should be separated with dash '-'; * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#standard-classes - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#standard-classes */ class ClassNamingSniff implements Sniff { @@ -35,7 +34,7 @@ class ClassNamingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -43,7 +42,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php index 39753544cf248..b5f61a6432731 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php @@ -14,8 +14,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-colon-indents - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-colon-indents */ class ColonSpacingSniff implements Sniff { @@ -27,7 +26,7 @@ class ColonSpacingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +34,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php index 83f2e8f5d94de..83e3da698f08f 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php @@ -13,8 +13,7 @@ * * Ensure that hexadecimal values are used for variables not for properties * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#hexadecimal-notation - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#hexadecimal-notation */ class ColourDefinitionSniff implements Sniff { @@ -26,7 +25,7 @@ class ColourDefinitionSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php index d27c853ab7808..a8ce4cfe547a1 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php @@ -13,8 +13,7 @@ * * Ensure that spaces are used before and after combinators * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#combinator-indents - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#combinator-indents */ class CombinatorIndentationSniff implements Sniff { @@ -26,7 +25,7 @@ class CombinatorIndentationSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php index abf15dd3748d5..41c970d4cb496 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php @@ -15,8 +15,7 @@ * First, second and third level comments should have two spaces after "//". * Inline comments should have one space after "//". * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#comments - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#comments */ class CommentLevelsSniff implements Sniff { @@ -42,7 +41,7 @@ class CommentLevelsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -50,7 +49,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php index 54bd8b70d5c59..db03440699f41 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php @@ -13,8 +13,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#important-property - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#important-property */ class ImportantPropertySniff implements Sniff { @@ -26,7 +25,7 @@ class ImportantPropertySniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php index 3ff268acf0c13..f0f994131e635 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php @@ -14,7 +14,7 @@ * Ensures styles are indented 4 spaces. * * @see Squiz_Sniffs_CSS_IndentationSniff - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#indentation + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#indentation */ class IndentationSniff implements Sniff { @@ -46,7 +46,7 @@ class IndentationSniff implements Sniff private $styleCodesToSkip = [T_ASPERAND, T_COLON, T_OPEN_PARENTHESIS, T_CLOSE_PARENTHESIS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -54,7 +54,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php index 3f33d0b2c69d5..e9bd1f7942ed4 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php @@ -13,8 +13,7 @@ * * Start each property declaration in a new line * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-line-break - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-line-break */ class PropertiesLineBreakSniff implements Sniff { @@ -26,7 +25,7 @@ class PropertiesLineBreakSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php index e59b5da37d0e2..a39974f2b3861 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php @@ -13,8 +13,7 @@ * * Ensure that properties are sorted alphabetically * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#sorting - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#sorting */ class PropertiesSortingSniff implements Sniff { @@ -43,7 +42,7 @@ class PropertiesSortingSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -57,7 +56,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php index 92ea5b420658f..8127d26770410 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php @@ -13,8 +13,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#quotes - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#quotes */ class QuotesSniff implements Sniff { @@ -26,7 +25,7 @@ class QuotesSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php index 4660786669815..99b91b63d88fc 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php @@ -14,8 +14,7 @@ * Ensure that a line break exists after each selector delimiter. * No spaces should be before or after delimiters. * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selector-delimiters - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selector-delimiters */ class SelectorDelimiterSniff implements Sniff { @@ -27,7 +26,7 @@ class SelectorDelimiterSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +34,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -50,6 +49,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Parenthesis validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php index c1cd6cde33406..de30d9cdbf497 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php @@ -13,8 +13,7 @@ * * Property should have a semicolon at the end of line * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#end-of-the-property-line - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#end-of-the-property-line */ class SemicolonSpacingSniff implements Sniff { @@ -44,7 +43,7 @@ class SemicolonSpacingSniff implements Sniff private $styleCodesToSkip = [T_ASPERAND, T_COLON, T_OPEN_PARENTHESIS, T_CLOSE_PARENTHESIS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -52,7 +51,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -72,6 +71,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Semicolon validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens @@ -90,6 +91,8 @@ private function validateSemicolon(File $phpcsFile, $stackPtr, array $tokens, $s } /** + * Spaces validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php b/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php index 9da92913b402f..db6a2fe116924 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php @@ -7,7 +7,6 @@ /** * Interface TokenizerSymbolsInterface - * */ interface TokenizerSymbolsInterface { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php index 88a275baec2a0..16ebb71c7281a 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php @@ -13,8 +13,7 @@ * * Ensure that selector in one line, concatenation is not used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#formatting-1 - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#formatting-1 */ class TypeSelectorConcatenationSniff implements Sniff { @@ -34,7 +33,7 @@ class TypeSelectorConcatenationSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -42,7 +41,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php index 3983cf54e05fd..3cd0f5d1679be 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php @@ -18,8 +18,7 @@ * - Type selectors must be lowercase * - Write selector in one line, do not use concatenation * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selectors-naming - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selectors-naming */ class TypeSelectorsSniff implements Sniff { @@ -56,7 +55,7 @@ class TypeSelectorsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -64,7 +63,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php index 8a559da88e6f2..433c256c5bdad 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php @@ -16,9 +16,8 @@ * they should be located in the module file, in the beginning of the general comment. * - All variable names must be lowercase * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#local-variables - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#naming - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#local-variables + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#naming */ class VariablesSniff implements Sniff { @@ -30,7 +29,7 @@ class VariablesSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -38,7 +37,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php index 1b4fb53c45010..a19a0a8eb7016 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php @@ -14,9 +14,8 @@ * Ensure that units for 0 is not specified * Omit leading "0"s in values, use dot instead * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#and-units - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#floating-values - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#and-units + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#floating-values */ class ZeroUnitsSniff implements Sniff { @@ -43,7 +42,7 @@ class ZeroUnitsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -51,7 +50,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php index 1618beb665d4b..6a7a0d2b1432c 100644 --- a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php @@ -8,12 +8,15 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +/** + * Validates that interface name ends with "Interface" suffix. + */ class InterfaceNameSniff implements Sniff { const INTERFACE_SUFFIX = 'Interface'; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -21,7 +24,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php index e3cfdf532438c..b81c250338e1d 100644 --- a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php @@ -8,6 +8,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +/** + * Validates that class name is not reserved word. + */ class ReservedWordsSniff implements Sniff { /** @@ -35,7 +38,7 @@ class ReservedWordsSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -94,7 +97,7 @@ protected function validateClass(File $sourceFile, $stackPtr) } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php b/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php index 0482f574f28ce..54cf1d462c300 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php @@ -52,7 +52,7 @@ class ExecutableRegExSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -60,7 +60,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php b/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php index d98a55a6f3ee8..aea1ddc1efa91 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php @@ -106,7 +106,7 @@ class StringPositionSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -114,7 +114,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php b/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php index b1e358e404829..50fddb240b30b 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php @@ -5,8 +5,8 @@ */ namespace Magento\Sniffs\Translation; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; /** * Make sure that constants are not used as the first argument of translation function. @@ -21,7 +21,7 @@ class ConstantUsageSniff implements Sniff protected $previousLineContent = ''; /** - * {@inheritDoc} + * @inheritdoc */ public function register() { @@ -31,7 +31,11 @@ public function register() /** * Copied from \Generic_Sniffs_Files_LineLengthSniff, minor changes made * - * {@inheritDoc} + * {@inheritdoc} + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile + * @param int $stackPtr + * @return void|int */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php b/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php index de3cfc50bbb56..8e34727533bdd 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php @@ -14,7 +14,7 @@ class EmptyLineMissedSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -22,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -37,6 +37,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Execute empty line missed check. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php index f276426efad6f..c862b019ae10d 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php @@ -14,7 +14,7 @@ class MultipleEmptyLinesSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -22,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/tests/unit/phpunit.xml.dist b/dev/tests/static/framework/tests/unit/phpunit.xml.dist index 57c13a6b3931b..aca48f6f8d1d0 100644 --- a/dev/tests/static/framework/tests/unit/phpunit.xml.dist +++ b/dev/tests/static/framework/tests/unit/phpunit.xml.dist @@ -20,4 +20,32 @@ <php> <ini name="date.timezone" value="America/Los_Angeles"/> </php> + <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="magentoAdminConfigFixture"> + <string>magentoAdminConfigFixture</string> + </element> + <element key="magentoAppIsolation"> + <string>magentoAppIsolation</string> + </element> + <element key="magentoComponentsDir"> + <string>magentoComponentsDir</string> + </element> + <element key="magentoConfigFixture"> + <string>magentoConfigFixture</string> + </element> + <element key="@magentoDbIsolation"> + <string>magentoDataFixture</string> + </element> + <element key="magentoDbIsolation"> + <string>magentoDbIsolation</string> + </element> + </array> + </arguments> + </listener> + </listeners> </phpunit> diff --git a/dev/tests/static/phpunit-all.xml.dist b/dev/tests/static/phpunit-all.xml.dist index 3020889e6066b..7f067d9290f3a 100644 --- a/dev/tests/static/phpunit-all.xml.dist +++ b/dev/tests/static/phpunit-all.xml.dist @@ -23,4 +23,12 @@ <!-- TESTCODESTYLE_IS_FULL_SCAN - specify if full scan should be performed for test code style test --> <const name="TESTCODESTYLE_IS_FULL_SCAN" value="0"/> </php> + <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + </arguments> + </listener> + </listeners> </phpunit> diff --git a/dev/tests/static/phpunit.xml.dist b/dev/tests/static/phpunit.xml.dist index ec581ec992f00..8ed80df002dfd 100644 --- a/dev/tests/static/phpunit.xml.dist +++ b/dev/tests/static/phpunit.xml.dist @@ -35,4 +35,12 @@ <!-- TESTS_COMPOSER_PATH - specify the path to composer binary, if a relative reference cannot be resolved --> <!--<const name="TESTS_COMPOSER_PATH" value="/usr/local/bin/composer"/>--> </php> + <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + </arguments> + </listener> + </listeners> </phpunit> diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/words_ce.xml b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/words_ce.xml index 4790ce24fd87f..9bb00533a5da5 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/words_ce.xml +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/words_ce.xml @@ -53,7 +53,7 @@ <word>overriden</word> </item> <item> - <path>composer.lock</path> + <path>dev/composer.lock</path> </item> <item> <path>app/code/Magento/Customer/view/frontend/web/js/zxcvbn.js</path> diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 8fa70be004fa9..9c40f33f27a12 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -205,3 +205,5 @@ Test/_files Magento/InventoryCatalogAdminUi/Controller/Adminhtml Magento/InventoryConfigurableProductIndexer/Indexer Magento/InventoryGroupedProductIndexer/Indexer +Magento/Customer/Model/FileUploaderDataResolver.php +Magento/Customer/Model/Customer/DataProvider.php diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml index 2d9eb7478ce91..7a402818eb0b9 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml @@ -48,6 +48,6 @@ <!-- Magento Specific Rules --> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/FinalImplementation" /> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/AllPurposeAction" /> - <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/RequestAwareBlockMethod" /> + <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/CookieAndSessionMisuse" /> </ruleset> diff --git a/dev/tests/unit/phpunit.xml.dist b/dev/tests/unit/phpunit.xml.dist index d35f7fc1819fb..102c9c41505e2 100644 --- a/dev/tests/unit/phpunit.xml.dist +++ b/dev/tests/unit/phpunit.xml.dist @@ -40,6 +40,26 @@ </whitelist> </filter> <listeners> + <listener class="Yandex\Allure\Adapter\AllureAdapter"> + <arguments> + <string>var/allure-results</string> <!-- XML files output directory --> + <boolean>true</boolean> <!-- Whether to delete previous results on rerun --> + <array> <!-- A list of custom annotations to ignore (optional) --> + <element key="codingStandardsIgnoreStart"> + <string>codingStandardsIgnoreStart</string> + </element> + <element key="codingStandardsIgnoreEnd"> + <string>codingStandardsIgnoreEnd</string> + </element> + <element key="cover"> + <string>cover</string> + </element> + <element key="expectedExceptionMessageRegExp"> + <string>expectedExceptionMessageRegExp</string> + </element> + </array> + </arguments> + </listener> <listener class="Magento\Framework\TestFramework\Unit\Listener\ReplaceObjectManager"/> </listeners> <logging> diff --git a/dev/tools/UpgradeScripts/pre_composer_update_2.3.php b/dev/tools/UpgradeScripts/pre_composer_update_2.3.php index 6fe629e717b93..04e64ba45ba0e 100644 --- a/dev/tools/UpgradeScripts/pre_composer_update_2.3.php +++ b/dev/tools/UpgradeScripts/pre_composer_update_2.3.php @@ -148,7 +148,7 @@ } else { foreach ($composerData['repositories'] as $label => $repo) { - if (strpos(strtolower($label), 'mage') !== false || strpos($repo['url'], '.mage') !== false) { + if (is_string($label) && strpos(strtolower($label), 'mage') !== false || strpos($repo['url'], '.mage') !== false) { $mageUrls[] = $repo['url']; } } diff --git a/lib/internal/Magento/Framework/App/ScopeDefault.php b/lib/internal/Magento/Framework/App/ScopeDefault.php index 2ea62387145bf..e62d19f9ffbb4 100644 --- a/lib/internal/Magento/Framework/App/ScopeDefault.php +++ b/lib/internal/Magento/Framework/App/ScopeDefault.php @@ -17,7 +17,7 @@ class ScopeDefault implements ScopeInterface */ public function getCode() { - return 'default'; + return ''; } /** @@ -27,7 +27,7 @@ public function getCode() */ public function getId() { - return 1; + return 0; } /** diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php b/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php index 14531deda45bb..4820b83ceb7a8 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem/Rollback/Fs.php @@ -75,7 +75,7 @@ public function run() /** * we need these fake initializations because all magento's files in filesystem will be deleted and autoloader - * wont be able to load classes that we need for unpacking + * won't be able to load classes that we need for unpacking */ new Tar(); new Gz(); @@ -94,6 +94,8 @@ public function run() } /** + * Get file system helper instance + * * @return Helper * @deprecated 100.2.0 */ diff --git a/lib/internal/Magento/Framework/Cache/Backend/Database.php b/lib/internal/Magento/Framework/Cache/Backend/Database.php index 291078383014a..231a8584cc8a5 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/Database.php +++ b/lib/internal/Magento/Framework/Cache/Backend/Database.php @@ -27,11 +27,11 @@ * ) ENGINE=InnoDB DEFAULT CHARSET=utf8; */ -/** - * Database cache backend - */ namespace Magento\Framework\Cache\Backend; +/** + * Database cache backend. + */ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_ExtendedInterface { /** @@ -139,7 +139,7 @@ protected function _getTagsTable() * * Note : return value is always "string" (unserialization is done by the core not by the backend) * - * @param string $id Cache id + * @param string $id Cache id * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @return string|false cached datas */ @@ -432,7 +432,7 @@ public function touch($id, $extraLifetime) return $this->_getConnection()->update( $this->_getDataTable(), ['expire_time' => new \Zend_Db_Expr('expire_time+' . $extraLifetime)], - ['id=?' => $id, 'expire_time = 0 OR expire_time>' => time()] + ['id=?' => $id, 'expire_time = 0 OR expire_time>?' => time()] ); } else { return true; diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 9c789e81913c4..dbafc9734e091 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -7,6 +7,7 @@ use Magento\Framework\Data\Collection\EntityFactoryInterface; use Magento\Framework\Option\ArrayInterface; +use Magento\Framework\Exception\InputException; /** * Data collection @@ -234,12 +235,20 @@ protected function _setIsLoaded($flag = true) * Get current collection page * * @param int $displacement + * @throws \Magento\Framework\Exception\InputException * @return int */ public function getCurPage($displacement = 0) { if ($this->_curPage + $displacement < 1) { return 1; + } elseif ($this->_curPage > $this->getLastPageNumber() && $displacement === 0) { + throw new InputException( + __( + 'currentPage value %1 specified is greater than the %2 page(s) available.', + [$this->_curPage, $this->getLastPageNumber()] + ) + ); } elseif ($this->_curPage + $displacement > $this->getLastPageNumber()) { return $this->getLastPageNumber(); } else { diff --git a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php index a8451e43ade20..3638ff921fa9d 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php @@ -201,6 +201,8 @@ public function setType($type) } /** + * Set form. + * * @param AbstractForm $form * @return $this */ @@ -238,6 +240,7 @@ public function getHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', @@ -326,6 +329,8 @@ public function getRenderer() } /** + * Get Ui Id. + * * @param null|string $suffix * @return string */ diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Date.php b/lib/internal/Magento/Framework/Data/Form/Element/Date.php index ed5457edc7f3f..897617e560be5 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Date.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Date.php @@ -9,11 +9,15 @@ * * @author Magento Core Team <core@magentocommerce.com> */ + namespace Magento\Framework\Data\Form\Element; use Magento\Framework\Escaper; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +/** + * Date element + */ class Date extends AbstractElement { /** @@ -50,8 +54,7 @@ public function __construct( } /** - * If script executes on x64 system, converts large - * numeric values to timestamp limit + * If script executes on x64 system, converts large numeric values to timestamp limit * * @param int $value * @return int @@ -82,13 +85,14 @@ public function setValue($value) $this->_value = $value; return $this; } - if (preg_match('/^[0-9]+$/', $value)) { - $this->_value = (new \DateTime())->setTimestamp($this->_toTimestamp($value)); - return $this; - } try { - $this->_value = new \DateTime($value, new \DateTimeZone($this->localeDate->getConfigTimezone())); + if (preg_match('/^[0-9]+$/', $value)) { + $this->_value = (new \DateTime())->setTimestamp($this->_toTimestamp($value)); + } else { + $this->_value = new \DateTime($value); + $this->_value->setTimezone(new \DateTimeZone($this->localeDate->getConfigTimezone())); + } } catch (\Exception $e) { $this->_value = ''; } @@ -97,6 +101,7 @@ public function setValue($value) /** * Get date value as string. + * * Format can be specified, or it will be taken from $this->getFormat() * * @param string $format (compatible with \DateTime) @@ -146,7 +151,7 @@ public function getValueInstance() */ public function getElementHtml() { - $this->addClass('admin__control-text input-text'); + $this->addClass('admin__control-text input-text input-date'); $dateFormat = $this->getDateFormat() ?: $this->getFormat(); $timeFormat = $this->getTimeFormat(); if (empty($dateFormat)) { diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php index 5608959c110ff..80c256d8553ef 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Data\Test\Unit; +/** + * Class for Collection test. + */ class CollectionTest extends \PHPUnit\Framework\TestCase { /** @@ -12,6 +15,9 @@ class CollectionTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * Set up. + */ protected function setUp() { $this->_model = new \Magento\Framework\Data\Collection( @@ -19,6 +25,11 @@ protected function setUp() ); } + /** + * Test for method removeAllItems. + * + * @return void + */ public function testRemoveAllItems() { $this->_model->addItem(new \Magento\Framework\DataObject()); @@ -30,6 +41,7 @@ public function testRemoveAllItems() /** * Test loadWithFilter() + * * @return void */ public function testLoadWithFilter() @@ -42,6 +54,8 @@ public function testLoadWithFilter() } /** + * Test for method etItemObjectClass + * * @dataProvider setItemObjectClassDataProvider */ public function testSetItemObjectClass($class) @@ -51,6 +65,8 @@ public function testSetItemObjectClass($class) } /** + * Data provider. + * * @return array */ public function setItemObjectClassDataProvider() @@ -59,6 +75,8 @@ public function setItemObjectClassDataProvider() } /** + * Test for method setItemObjectClass with exception. + * * @expectedException \InvalidArgumentException * @expectedExceptionMessage Incorrect_ClassName does not extend \Magento\Framework\DataObject */ @@ -67,12 +85,22 @@ public function testSetItemObjectClassException() $this->_model->setItemObjectClass('Incorrect_ClassName'); } + /** + * Test for method addFilter. + * + * @return void + */ public function testAddFilter() { $this->_model->addFilter('field1', 'value'); $this->assertEquals('field1', $this->_model->getFilter('field1')->getData('field')); } + /** + * Test for method getFilters. + * + * @return void + */ public function testGetFilters() { $this->_model->addFilter('field1', 'value'); @@ -81,12 +109,22 @@ public function testGetFilters() $this->assertEquals('field2', $this->_model->getFilter(['field1', 'field2'])[1]->getData('field')); } + /** + * Test for method get non existion filters. + * + * @return void + */ public function testGetNonExistingFilters() { $this->assertEmpty($this->_model->getFilter([])); $this->assertEmpty($this->_model->getFilter('non_existing_filter')); } + /** + * Test for lag. + * + * @return void + */ public function testFlag() { $this->_model->setFlag('flag_name', 'flag_value'); @@ -95,12 +133,35 @@ public function testFlag() $this->assertNull($this->_model->getFlag('non_existing_flag')); } + /** + * Test for method getCurPage. + * + * @return void + */ public function testGetCurPage() { - $this->_model->setCurPage(10); + $this->_model->setCurPage(1); $this->assertEquals(1, $this->_model->getCurPage()); } + /** + * Test for getCurPage with exception. + * + * @expectedException \Magento\Framework\Exception\StateException + * @return void + */ + public function testGetCurPageWithException() + { + $this->_model->setCurPage(10); + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->_model->getCurPage(); + } + + /** + * Test for method possibleFlowWithItem. + * + * @return void + */ public function testPossibleFlowWithItem() { $firstItemMock = $this->createPartialMock( @@ -168,6 +229,11 @@ public function testPossibleFlowWithItem() $this->assertEquals([], $this->_model->getItems()); } + /** + * Test for method eachCallsMethodOnEachItemWithNoArgs. + * + * @return void + */ public function testEachCallsMethodOnEachItemWithNoArgs() { for ($i = 0; $i < 3; $i++) { @@ -177,7 +243,12 @@ public function testEachCallsMethodOnEachItemWithNoArgs() } $this->_model->each('testCallback'); } - + + /** + * Test for method eachCallsMethodOnEachItemWithArgs. + * + * @return void + */ public function testEachCallsMethodOnEachItemWithArgs() { for ($i = 0; $i < 3; $i++) { @@ -188,6 +259,11 @@ public function testEachCallsMethodOnEachItemWithArgs() $this->_model->each('testCallback', ['a', 'b', 'c']); } + /** + * Test for method callsClosureWithEachItemAndNoArgs. + * + * @return void + */ public function testCallsClosureWithEachItemAndNoArgs() { for ($i = 0; $i < 3; $i++) { @@ -200,6 +276,11 @@ public function testCallsClosureWithEachItemAndNoArgs() }); } + /** + * Test for method callsClosureWithEachItemAndArgs. + * + * @return void + */ public function testCallsClosureWithEachItemAndArgs() { for ($i = 0; $i < 3; $i++) { @@ -212,6 +293,11 @@ public function testCallsClosureWithEachItemAndArgs() }, ['a', 'b', 'c']); } + /** + * Test for method callsCallableArrayWithEachItemNoArgs. + * + * @return void + */ public function testCallsCallableArrayWithEachItemNoArgs() { $mockCallbackObject = $this->getMockBuilder('DummyEachCallbackInstance') @@ -230,6 +316,11 @@ public function testCallsCallableArrayWithEachItemNoArgs() $this->_model->each([$mockCallbackObject, 'testObjCallback']); } + /** + * Test for method callsCallableArrayWithEachItemAndArgs. + * + * @return void + */ public function testCallsCallableArrayWithEachItemAndArgs() { $mockCallbackObject = $this->getMockBuilder('DummyEachCallbackInstance') diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php index e29b1dcf441e4..a85c1f4aa450c 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php @@ -195,6 +195,7 @@ public function testGetHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', diff --git a/lib/internal/Magento/Framework/Escaper.php b/lib/internal/Magento/Framework/Escaper.php index ae6872ab0c6e7..19a9c0c1788fc 100644 --- a/lib/internal/Magento/Framework/Escaper.php +++ b/lib/internal/Magento/Framework/Escaper.php @@ -91,6 +91,7 @@ function ($errorNumber, $errorString) { throw new \Exception($errorString, $errorNumber); } ); + $data = $this->prepareUnescapedCharacters($data); $string = mb_convert_encoding($data, 'HTML-ENTITIES', 'UTF-8'); try { $domDocument->loadHTML( @@ -119,6 +120,19 @@ function ($errorNumber, $errorString) { return $result; } + /** + * Used to replace characters, that mb_convert_encoding will not process + * + * @param string $data + * @return string|null + */ + private function prepareUnescapedCharacters(string $data): ?string + { + $patterns = ['/\&/u']; + $replacements = ['&']; + return \preg_replace($patterns, $replacements, $data); + } + /** * Remove not allowed tags * diff --git a/lib/internal/Magento/Framework/Mail/Message.php b/lib/internal/Magento/Framework/Mail/Message.php index 6f156e42dfdba..71da6e673bf29 100644 --- a/lib/internal/Magento/Framework/Mail/Message.php +++ b/lib/internal/Magento/Framework/Mail/Message.php @@ -8,6 +8,9 @@ use Zend\Mime\Mime; use Zend\Mime\Part; +/** + * Class Message for email transportation + */ class Message implements MailMessageInterface { /** @@ -34,7 +37,7 @@ public function __construct($charset = 'utf-8') } /** - * {@inheritdoc} + * @inheritdoc * * @deprecated * @see \Magento\Framework\Mail\Message::setBodyText @@ -47,7 +50,7 @@ public function setMessageType($type) } /** - * {@inheritdoc} + * @inheritdoc * * @deprecated * @see \Magento\Framework\Mail\Message::setBodyText @@ -63,7 +66,7 @@ public function setBody($body) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubject($subject) { @@ -72,7 +75,7 @@ public function setSubject($subject) } /** - * {@inheritdoc} + * @inheritdoc */ public function getSubject() { @@ -80,7 +83,7 @@ public function getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function getBody() { @@ -88,16 +91,29 @@ public function getBody() } /** - * {@inheritdoc} + * @inheritdoc + * + * @deprecated This function is missing the from name. The + * setFromAddress() function sets both from address and from name. + * @see setFromAddress() */ public function setFrom($fromAddress) { - $this->zendMessage->setFrom($fromAddress); + $this->setFromAddress($fromAddress, null); + return $this; + } + + /** + * @inheritdoc + */ + public function setFromAddress($fromAddress, $fromName = null) + { + $this->zendMessage->setFrom($fromAddress, $fromName); return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public function addTo($toAddress) { @@ -106,7 +122,7 @@ public function addTo($toAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function addCc($ccAddress) { @@ -115,7 +131,7 @@ public function addCc($ccAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function addBcc($bccAddress) { @@ -124,7 +140,7 @@ public function addBcc($bccAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function setReplyTo($replyToAddress) { @@ -133,7 +149,7 @@ public function setReplyTo($replyToAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function getRawMessage() { @@ -157,7 +173,7 @@ private function createHtmlMimeFromString($htmlBody) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBodyHtml($html) { @@ -166,7 +182,7 @@ public function setBodyHtml($html) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBodyText($text) { diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php index eac5d74c6e3dc..a7bb96122a84d 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php @@ -17,6 +17,8 @@ use Magento\Framework\Phrase; /** + * TransportBuilder + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -175,13 +177,30 @@ public function setReplyTo($email, $name = null) /** * Set mail from address * + * @deprecated This function sets the from address for the first store only. + * new function setFromByStore introduced to allow setting of from address + * based on store. + * @see setFromByStore() + * * @param string|array $from * @return $this */ public function setFrom($from) { - $result = $this->_senderResolver->resolve($from); - $this->message->setFrom($result['email'], $result['name']); + return $this->setFromByStore($from, null); + } + + /** + * Set mail from address by store + * + * @param string|array $from + * @param string|int $store + * @return $this + */ + public function setFromByStore($from, $store = null) + { + $result = $this->_senderResolver->resolve($from, $store); + $this->message->setFromAddress($result['email'], $result['name']); return $this; } diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php index 785c93824a57d..85b1b181d4f9e 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php @@ -8,6 +8,13 @@ use Magento\Framework\Mail\MessageInterface; +/** + * Class TransportBuilderByStore + * + * @deprecated The ability to set From address based on store is now available + * in the \Magento\Framework\Mail\Template\TransportBuilder class + * @see \Magento\Framework\Mail\Template\TransportBuilder::setFromByStore + */ class TransportBuilderByStore { /** @@ -47,7 +54,7 @@ public function __construct( public function setFromByStore($from, $store) { $result = $this->senderResolver->resolve($from, $store); - $this->message->setFrom($result['email'], $result['name']); + $this->message->setFromAddress($result['email'], $result['name']); return $this; } diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php index 80df2887a3a93..a28dbcd291baf 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php @@ -55,8 +55,8 @@ public function testSetFromByStore() ->with($sender, $store) ->willReturn($sender); $this->messageMock->expects($this->once()) - ->method('setFrom') - ->with('from@example.com', 'name') + ->method('setFromAddress') + ->with($sender['email'], $sender['name']) ->willReturnSelf(); $this->model->setFromByStore($sender, $store); diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php index 596640480c823..b476eecd7f59f 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php @@ -167,19 +167,20 @@ public function getTransportDataProvider() /** * @return void */ - public function testSetFrom() + public function testSetFromByStore() { $sender = ['email' => 'from@example.com', 'name' => 'name']; + $store = 1; $this->senderResolverMock->expects($this->once()) ->method('resolve') - ->with($sender) + ->with($sender, $store) ->willReturn($sender); $this->messageMock->expects($this->once()) - ->method('setFrom') - ->with('from@example.com', 'name') + ->method('setFromAddress') + ->with($sender['email'], $sender['name']) ->willReturnSelf(); - $this->builder->setFrom($sender); + $this->builder->setFromByStore($sender, $store); } /** diff --git a/lib/internal/Magento/Framework/Pricing/PriceCurrencyInterface.php b/lib/internal/Magento/Framework/Pricing/PriceCurrencyInterface.php index 31f00d8b1345a..eb379b54d257f 100644 --- a/lib/internal/Magento/Framework/Pricing/PriceCurrencyInterface.php +++ b/lib/internal/Magento/Framework/Pricing/PriceCurrencyInterface.php @@ -78,6 +78,7 @@ public function convertAndFormat( /** * Round price * + * @deprecated * @param float $price * @return float */ @@ -93,6 +94,8 @@ public function round($price); public function getCurrency($scope = null, $currency = null); /** + * Get currency symbol + * * @param null|string|bool|int|\Magento\Framework\App\ScopeInterface $scope * @param \Magento\Framework\Model\AbstractModel|string|null $currency * @return string diff --git a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php index 6311003bd2ad5..2f3caf08c534e 100644 --- a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php @@ -40,25 +40,33 @@ class DataObjectProcessor */ private $customAttributesProcessor; + /** + * @var array + */ + private $processors; + /** * @param MethodsMap $methodsMapProcessor * @param TypeCaster $typeCaster * @param FieldNamer $fieldNamer * @param CustomAttributesProcessor $customAttributesProcessor * @param ExtensionAttributesProcessor $extensionAttributesProcessor + * @param array $processors */ public function __construct( MethodsMap $methodsMapProcessor, TypeCaster $typeCaster, FieldNamer $fieldNamer, CustomAttributesProcessor $customAttributesProcessor, - ExtensionAttributesProcessor $extensionAttributesProcessor + ExtensionAttributesProcessor $extensionAttributesProcessor, + array $processors = [] ) { $this->methodsMapProcessor = $methodsMapProcessor; $this->typeCaster = $typeCaster; $this->fieldNamer = $fieldNamer; $this->extensionAttributesProcessor = $extensionAttributesProcessor; $this->customAttributesProcessor = $customAttributesProcessor; + $this->processors = $processors; } /** @@ -121,6 +129,27 @@ public function buildOutputDataArray($dataObject, $dataObjectType) $outputData[$key] = $value; } + + $outputData = $this->changeOutputArray($dataObject, $outputData); + + return $outputData; + } + + /** + * Change output array if needed. + * + * @param mixed $dataObject + * @param array $outputData + * @return array + */ + private function changeOutputArray($dataObject, array $outputData): array + { + foreach ($this->processors as $dataObjectClassName => $processor) { + if ($dataObject instanceof $dataObjectClassName) { + $outputData = $processor->execute($dataObject, $outputData); + } + } + return $outputData; } } diff --git a/lib/internal/Magento/Framework/Serialize/README.md b/lib/internal/Magento/Framework/Serialize/README.md index 5af8fb7f71b6b..d900f89208a54 100644 --- a/lib/internal/Magento/Framework/Serialize/README.md +++ b/lib/internal/Magento/Framework/Serialize/README.md @@ -3,6 +3,7 @@ **Serialize** library provides interface *SerializerInterface* and multiple implementations: * *Json* - default implementation. Uses PHP native json_encode/json_decode functions; + * *JsonHexTag* - default implementation. Uses PHP native json_encode/json_decode functions with `JSON_HEX_TAG` option enabled; * *Serialize* - less secure than *Json*, but gives higher performance on big arrays. Uses PHP native serialize/unserialize functions, does not unserialize objects on PHP 7. Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities. \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php new file mode 100644 index 0000000000000..4a5406ff3fd99 --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Serializer; + +use Magento\Framework\Serialize\SerializerInterface; + +/** + * Serialize data to JSON with the JSON_HEX_TAG option enabled + * (All < and > are converted to \u003C and \u003E), + * unserialize JSON encoded data + * + * @api + * @since 100.2.0 + */ +class JsonHexTag extends Json implements SerializerInterface +{ + /** + * @inheritDoc + * @since 100.2.0 + */ + public function serialize($data): string + { + $result = json_encode($data, JSON_HEX_TAG); + if (false === $result) { + throw new \InvalidArgumentException('Unable to serialize value.'); + } + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php new file mode 100644 index 0000000000000..c867dced0fc6e --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Test\Unit\Serializer; + +use Magento\Framework\DataObject; +use Magento\Framework\Serialize\Serializer\JsonHexTag; + +class JsonHexTagTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $json; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->json = $objectManager->getObject(JsonHexTag::class); + } + + /** + * @param string|int|float|bool|array|null $value + * @param string $expected + * @dataProvider serializeDataProvider + */ + public function testSerialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->serialize($value) + ); + } + + public function serializeDataProvider() + { + $dataObject = new DataObject(['something']); + return [ + ['', '""'], + ['string', '"string"'], + [null, 'null'], + [false, 'false'], + [['a' => 'b', 'd' => 123], '{"a":"b","d":123}'], + [123, '123'], + [10.56, '10.56'], + [$dataObject, '{}'], + ['< >', '"\u003C \u003E"'], + ]; + } + + /** + * @param string $value + * @param string|int|float|bool|array|null $expected + * @dataProvider unserializeDataProvider + */ + public function testUnserialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->unserialize($value) + ); + } + + /** + * @return array + */ + public function unserializeDataProvider(): array + { + return [ + ['""', ''], + ['"string"', 'string'], + ['null', null], + ['false', false], + ['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]], + ['123', 123], + ['10.56', 10.56], + ['{}', []], + ['"\u003C \u003E"', '< >'], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to serialize value. + */ + public function testSerializeException() + { + $this->json->serialize(STDOUT); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to unserialize value. + * @dataProvider unserializeExceptionDataProvider + */ + public function testUnserializeException($value) + { + $this->json->unserialize($value); + } + + /** + * @return array + */ + public function unserializeExceptionDataProvider(): array + { + return [ + [''], + [false], + [null], + ['{'] + ]; + } +} diff --git a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php index 30d373822de57..1599cae073f30 100644 --- a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php @@ -69,9 +69,9 @@ public function testEscapeJsEscapesOwaspRecommendedRanges() // Exceptions to escaping ranges $immune = [',', '.', '_']; for ($chr = 0; $chr < 0xFF; $chr++) { - if ($chr >= 0x30 && $chr <= 0x39 - || $chr >= 0x41 && $chr <= 0x5A - || $chr >= 0x61 && $chr <= 0x7A + if (($chr >= 0x30 && $chr <= 0x39) + || ($chr >= 0x41 && $chr <= 0x5A) + || ($chr >= 0x61 && $chr <= 0x7A) ) { $literal = $this->codepointToUtf8($chr); $this->assertEquals($literal, $this->escaper->escapeJs($literal)); @@ -171,6 +171,11 @@ public function escapeHtmlDataProvider() 'data' => '&<>"\'&<>"' ', 'expected' => '&<>"'&<>"' ' ], + 'text with special characters and allowed tag' => [ + 'data' => '&<br/>"\'&<>"' ', + 'expected' => '&<br>"'&<>"' ', + 'allowedTags' => ['br'], + ], 'text with multiple allowed tags, includes self closing tag' => [ 'data' => '<span>some text in tags<br /></span>', 'expected' => '<span>some text in tags<br></span>', diff --git a/lib/internal/Magento/Framework/View/Element/ComponentVisibilityInterface.php b/lib/internal/Magento/Framework/View/Element/ComponentVisibilityInterface.php new file mode 100644 index 0000000000000..29d14553481d7 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Element/ComponentVisibilityInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\View\Element; + +/** + * Interface which allows to modify visibility behavior of UI components + */ +interface ComponentVisibilityInterface +{ + /** + * Defines if the component can be shown + * + * @return bool + */ + public function isComponentVisible(): bool; +} diff --git a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php index 43bfd46c1193a..7aac210dcab89 100644 --- a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php +++ b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php @@ -5,6 +5,10 @@ */ namespace Magento\Framework\View\Element\Html\Link; +use Magento\Framework\App\DefaultPathInterface; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Element\Template\Context; + /** * Block representing link with two possible states. * "Current" state means link leads to URL equivalent to URL of currently displayed page. @@ -17,25 +21,25 @@ * @method null|bool getCurrent() * @method \Magento\Framework\View\Element\Html\Link\Current setCurrent(bool $value) */ -class Current extends \Magento\Framework\View\Element\Template +class Current extends Template { /** * Default path * - * @var \Magento\Framework\App\DefaultPathInterface + * @var DefaultPathInterface */ protected $_defaultPath; /** * Constructor * - * @param \Magento\Framework\View\Element\Template\Context $context - * @param \Magento\Framework\App\DefaultPathInterface $defaultPath + * @param Context $context + * @param DefaultPathInterface $defaultPath * @param array $data */ public function __construct( - \Magento\Framework\View\Element\Template\Context $context, - \Magento\Framework\App\DefaultPathInterface $defaultPath, + Context $context, + DefaultPathInterface $defaultPath, array $data = [] ) { parent::__construct($context, $data); @@ -56,18 +60,20 @@ public function getHref() * Get current mca * * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ private function getMca() { $routeParts = [ - 'module' => $this->_request->getModuleName(), - 'controller' => $this->_request->getControllerName(), - 'action' => $this->_request->getActionName(), + (string)$this->_request->getModuleName(), + (string)$this->_request->getControllerName(), + (string)$this->_request->getActionName(), ]; $parts = []; + $pathParts = explode('/', trim($this->_request->getPathInfo(), '/')); foreach ($routeParts as $key => $value) { - if (!empty($value) && $value != $this->_defaultPath->getPart($key)) { + if (isset($pathParts[$key]) && $pathParts[$key] === $value) { $parts[] = $value; } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php index 909748722a081..7070ec9d48c11 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php @@ -17,11 +17,6 @@ class CurrentTest extends \PHPUnit\Framework\TestCase */ protected $_requestMock; - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_defaultPathMock; - /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ @@ -32,7 +27,6 @@ protected function setUp() $this->_objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_urlBuilderMock = $this->createMock(\Magento\Framework\UrlInterface::class); $this->_requestMock = $this->createMock(\Magento\Framework\App\Request\Http::class); - $this->_defaultPathMock = $this->createMock(\Magento\Framework\App\DefaultPathInterface::class); } public function testGetUrl() @@ -60,29 +54,46 @@ public function testIsCurrentIfIsset() $this->assertTrue($link->isCurrent()); } + /** + * Test if the current url is the same as link path + * + * @return void + */ public function testIsCurrent() { - $path = 'test/path'; - $url = 'http://example.com/a/b'; - - $this->_requestMock->expects($this->once())->method('getModuleName')->will($this->returnValue('a')); - $this->_requestMock->expects($this->once())->method('getControllerName')->will($this->returnValue('b')); - $this->_requestMock->expects($this->once())->method('getActionName')->will($this->returnValue('d')); - $this->_defaultPathMock->expects($this->atLeastOnce())->method('getPart')->will($this->returnValue('d')); + $path = 'test/index'; + $url = 'http://example.com/test/index'; + + $this->_requestMock->expects($this->once()) + ->method('getPathInfo') + ->will($this->returnValue('/test/index/')); + $this->_requestMock->expects($this->once()) + ->method('getModuleName') + ->will($this->returnValue('test')); + $this->_requestMock->expects($this->once()) + ->method('getControllerName') + ->will($this->returnValue('index')); + $this->_requestMock->expects($this->once()) + ->method('getActionName') + ->will($this->returnValue('index')); + $this->_urlBuilderMock->expects($this->at(0)) + ->method('getUrl') + ->with($path) + ->will($this->returnValue($url)); + $this->_urlBuilderMock->expects($this->at(1)) + ->method('getUrl') + ->with('test/index') + ->will($this->returnValue($url)); - $this->_urlBuilderMock->expects($this->at(0))->method('getUrl')->with($path)->will($this->returnValue($url)); - $this->_urlBuilderMock->expects($this->at(1))->method('getUrl')->with('a/b')->will($this->returnValue($url)); - - $this->_requestMock->expects($this->once())->method('getControllerName')->will($this->returnValue('b')); /** @var \Magento\Framework\View\Element\Html\Link\Current $link */ $link = $this->_objectManager->getObject( \Magento\Framework\View\Element\Html\Link\Current::class, [ 'urlBuilder' => $this->_urlBuilderMock, - 'request' => $this->_requestMock, - 'defaultPath' => $this->_defaultPathMock + 'request' => $this->_requestMock ] ); + $link->setPath($path); $this->assertTrue($link->isCurrent()); } diff --git a/lib/web/css/source/lib/_forms.less b/lib/web/css/source/lib/_forms.less index 531cf3d34ad76..a22ec5f52db6d 100644 --- a/lib/web/css/source/lib/_forms.less +++ b/lib/web/css/source/lib/_forms.less @@ -300,6 +300,8 @@ input[type="checkbox"] { .lib-form-element-choice(@_type: input-checkbox); + position: relative; + top: 2px; } input[type="radio"] { diff --git a/lib/web/css/source/lib/_navigation.less b/lib/web/css/source/lib/_navigation.less index acae3c629500e..6232333fca33f 100644 --- a/lib/web/css/source/lib/_navigation.less +++ b/lib/web/css/source/lib/_navigation.less @@ -330,6 +330,19 @@ padding-right: 0; } + &:hover { + &:after { + content: ''; + display: block; + position: absolute; + top: 0; + left: 100%; + width: 10px; + height: calc(100% + 3px); + z-index: 1; + } + } + > .level-top { .lib-css(background, @_nav-level0-item-background-color); .lib-css(border, @_nav-level0-item-border); @@ -408,6 +421,17 @@ @_left: @_submenu-arrow-left ); + &:before { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 4px; + left: 0; + top: -4px; + z-index: 1; + } + a { display: block; line-height: inherit; diff --git a/lib/web/css/source/lib/variables/_buttons.less b/lib/web/css/source/lib/variables/_buttons.less index 8c31c16143859..8729eca6aca6d 100644 --- a/lib/web/css/source/lib/variables/_buttons.less +++ b/lib/web/css/source/lib/variables/_buttons.less @@ -57,14 +57,14 @@ @button-primary__gradient: false; @button-primary__gradient-direction: false; -@button-primary__background: @color-blue1; -@button-primary__border: 1px solid @color-blue1; +@button-primary__background: @theme__color__primary; +@button-primary__border: 1px solid @theme__color__primary; @button-primary__color: @color-white; @button-primary__gradient-color-start: false; @button-primary__gradient-color-end: false; -@button-primary__hover__background: @color-blue2; -@button-primary__hover__border: 1px solid @color-blue2; +@button-primary__hover__background: @theme__color__primary-alt; +@button-primary__hover__border: 1px solid @theme__color__primary-alt; @button-primary__hover__color: @button-primary__color; @button-primary__hover__gradient-color-start: false; @button-primary__hover__gradient-color-end: false; diff --git a/lib/web/css/source/lib/variables/_colors.less b/lib/web/css/source/lib/variables/_colors.less index 01e0db03adcdc..9c694468e9f62 100644 --- a/lib/web/css/source/lib/variables/_colors.less +++ b/lib/web/css/source/lib/variables/_colors.less @@ -78,7 +78,7 @@ @color-sky-blue1: #68a8e0; @color-pink1: #fae5e5; -@color-dark-pink1: #800080; +@color-dark-pink1: #800080; // Legacy pink @color-brownie1: #6f4400; @color-brownie-light1: #c07600; @@ -92,6 +92,10 @@ // Color nesting // --------------------------------------------- +@theme__color__primary: @color-blue1; +@theme__color__primary-alt: @color-blue2; +@theme__color__secondary: @color-orange-red1; + @primary__color: @color-gray20; @primary__color__dark: darken(@primary__color, 35%); // #000 @primary__color__darker: darken(@primary__color, 13.5%); // #111 @@ -104,5 +108,5 @@ @page__background-color: @color-white; @panel__background-color: darken(@page__background-color, 6%); -@active__color: @color-orange-red1; +@active__color: @theme__color__secondary; @error__color: @color-red10; diff --git a/lib/web/css/source/lib/variables/_navigation.less b/lib/web/css/source/lib/variables/_navigation.less index 2a4aa11014dce..3ef1742547426 100644 --- a/lib/web/css/source/lib/variables/_navigation.less +++ b/lib/web/css/source/lib/variables/_navigation.less @@ -23,7 +23,7 @@ @navigation-level0-item__text-decoration: none; @navigation-level0-item__active__background: ''; -@navigation-level0-item__active__border-color: @color-orange-red1; +@navigation-level0-item__active__border-color: @active__color; @navigation-level0-item__active__border-style: solid; @navigation-level0-item__active__border-width: 0 0 0 8px; @navigation-level0-item__active__color: ''; @@ -47,7 +47,7 @@ @submenu-item__active__background: ''; @submenu-item__active__border: 8px; -@submenu-item__active__border-color: @color-orange-red1; +@submenu-item__active__border-color: @active__color; @submenu-item__active__border-style: solid; @submenu-item__active__border-width: 0 0 0 @submenu-item__active__border; @submenu-item__active__color: ''; @@ -77,7 +77,7 @@ @navigation-desktop-level0-item__hover__text-decoration: @navigation-desktop-level0-item__text-decoration; @navigation-desktop-level0-item__active__background: ''; -@navigation-desktop-level0-item__active__border-color: @color-orange-red1; +@navigation-desktop-level0-item__active__border-color: @active__color; @navigation-desktop-level0-item__active__border-style: solid; @navigation-desktop-level0-item__active__border-width: 0 0 3px; @navigation-desktop-level0-item__active__color: @navigation-desktop-level0-item__hover__color; @@ -109,7 +109,7 @@ @submenu-desktop-item__hover__text-decoration: @navigation-desktop-level0-item__text-decoration; @submenu-desktop-item__active__background: ''; -@submenu-desktop-item__active__border-color: @color-orange-red1; +@submenu-desktop-item__active__border-color: @active__color; @submenu-desktop-item__active__border-style: solid; @submenu-desktop-item__active__border-width: 0 0 0 3px; @submenu-desktop-item__active__color: ''; diff --git a/lib/web/css/source/lib/variables/_rating.less b/lib/web/css/source/lib/variables/_rating.less index 2ffdab8beddfc..ec60d5916315c 100644 --- a/lib/web/css/source/lib/variables/_rating.less +++ b/lib/web/css/source/lib/variables/_rating.less @@ -14,6 +14,6 @@ @rating-icon__letter-spacing: -10px; @rating-icon__color: @color-gray78; -@rating-icon__active__color: @color-orange-red1; +@rating-icon__active__color: @active__color; @rating-label__hide: false; diff --git a/lib/web/css/source/lib/variables/_typography.less b/lib/web/css/source/lib/variables/_typography.less index bf88990fe05c8..205b1fa9c7a3b 100644 --- a/lib/web/css/source/lib/variables/_typography.less +++ b/lib/web/css/source/lib/variables/_typography.less @@ -83,13 +83,13 @@ // Links // --------------------------------------------- -@link__color: @color-blue1; +@link__color: @theme__color__primary; @link__text-decoration: none; @link__visited__color: @link__color; @link__visited__text-decoration: none; -@link__hover__color: @color-blue2; +@link__hover__color: @theme__color__primary-alt; @link__hover__text-decoration: underline; @link__active__color: @active__color; diff --git a/lib/web/fotorama/fotorama.js b/lib/web/fotorama/fotorama.js index 62a03110fa724..093ee707d3f98 100644 --- a/lib/web/fotorama/fotorama.js +++ b/lib/web/fotorama/fotorama.js @@ -1883,7 +1883,7 @@ fotoramaVersion = '4.6.4'; fotoramaData.fotorama = this; /** - * Search video items in incomming data and transform object for video layout. + * Search video items in incoming data and transform object for video layout. * */ function checkForVideo() { @@ -2022,7 +2022,7 @@ fotoramaVersion = '4.6.4'; } /** - * Set and install data from incomming @param {JSON} options or takes data attr from data-"name"=... values. + * Set and install data from incoming @param {JSON} options or takes data attr from data-"name"=... values. */ function setData() { data = that.data = data || clone(opts.data) || getDataFromHtml($fotorama); diff --git a/lib/web/jquery.js b/lib/web/jquery.js index e845f7db2ae57..9a98ef778ed58 100644 --- a/lib/web/jquery.js +++ b/lib/web/jquery.js @@ -7705,7 +7705,7 @@ value = hooks.expand( value ); delete props[ name ]; - // not quite $.extend, this wont overwrite keys already present. + // not quite $.extend, this won't overwrite keys already present. // also - reusing 'index' from above because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { diff --git a/lib/web/jquery/editableMultiselect/js/jquery.editable.js b/lib/web/jquery/editableMultiselect/js/jquery.editable.js index 87e5049db033f..6c9f0a4373dd5 100644 --- a/lib/web/jquery/editableMultiselect/js/jquery.editable.js +++ b/lib/web/jquery/editableMultiselect/js/jquery.editable.js @@ -129,7 +129,7 @@ return; } - /* prevent throwing an exeption if edit field is clicked again */ + /* prevent throwing an exception if edit field is clicked again */ if (self.editing) { return; } @@ -191,7 +191,7 @@ if (settings.style) { if ('inherit' == settings.style) { form.attr('style', $(self).attr('style')); - /* IE needs the second line or display wont be inherited */ + /* IE needs the second line or display won't be inherited */ form.css('display', $(self).css('display')); } else { form.attr('style', settings.style); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index ede8dd2081fdf..9779be85133f8 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -350,7 +350,7 @@ define([ * @param {String} content */ setContent: function (content) { - this.get(this.getId()).execCommand('mceSetContent', false, content); + this.get(this.getId()).setContent(content); }, /** @@ -559,10 +559,12 @@ define([ var selection = editor.selection, dom = editor.dom, rng = dom.createRng(), + doc = editor.getDoc(), markerHtml, marker; - if (!selection.getContent().length) { + // Validate the range we're trying to fix is contained within the current editors document + if (!selection.getContent().length && jQuery.contains(doc, selection.getRng().startContainer)) { markerHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; selection.setContent(markerHtml); marker = dom.get('mce_marker'); @@ -577,10 +579,10 @@ define([ * Update text area. */ updateTextArea: function () { - var editor = tinyMCE4.get(this.getId()), + var editor = this.get(this.getId()), content; - if (!editor) { + if (!editor || editor.id !== this.activeEditor().id) { return; } diff --git a/lib/web/mage/calendar.js b/lib/web/mage/calendar.js index ac154b333801d..a9ccf2cf787f9 100644 --- a/lib/web/mage/calendar.js +++ b/lib/web/mage/calendar.js @@ -66,6 +66,9 @@ * Widget calendar */ $.widget('mage.calendar', { + options: { + autoComplete: true + }, /** * Merge global options with options passed to widget invoke @@ -379,6 +382,9 @@ .addClass('v-middle') .text('') // Remove jQuery UI datepicker generated image .append('<span>' + pickerButtonText + '</span>'); + + $(element).attr('autocomplete', this.options.autoComplete ? 'on' : 'off'); + this._setCurrentDate(element); }, diff --git a/lib/web/mage/dataPost.js b/lib/web/mage/dataPost.js index 5d052f12db8fb..cc56ee266e08a 100644 --- a/lib/web/mage/dataPost.js +++ b/lib/web/mage/dataPost.js @@ -57,7 +57,7 @@ define([ */ postData: function (params) { var formKey = $(this.options.formKeyInputSelector).val(), - $form; + $form, input; if (formKey) { params.data['form_key'] = formKey; @@ -67,6 +67,19 @@ define([ data: params })); + if (params.files) { + $form[0].enctype = 'multipart/form-data'; + $.each(params.files, function (key, files) { + if (files instanceof FileList) { + input = document.createElement('input'); + input.type = 'file'; + input.name = key; + input.files = files; + $form[0].appendChild(input); + } + }); + } + if (params.data.confirmation) { uiConfirm({ content: params.data.confirmationMessage, diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index db98a1cc39a30..15c3d01cf2be3 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -472,8 +472,18 @@ define([ * @param {Array.<Object>} data - Set of gallery items to update. */ updateData: function (data) { + var mainImageIndex; + if (_.isArray(data)) { settings.fotoramaApi.load(data); + mainImageIndex = getMainImageIndex(data); + + if (mainImageIndex) { + settings.fotoramaApi.show({ + index: mainImageIndex, + time: 0 + }); + } $.extend(false, settings, { data: data, diff --git a/lib/web/mage/gallery/gallery.less b/lib/web/mage/gallery/gallery.less index ebd6bdd003b81..373708ac35a00 100644 --- a/lib/web/mage/gallery/gallery.less +++ b/lib/web/mage/gallery/gallery.less @@ -181,8 +181,8 @@ .fotorama__active { .fotorama__dot { - background-color: @color-orange-red1; - border-color: @color-orange-red1; + background-color: @active__color; + border-color: @active__color; } } @@ -237,7 +237,7 @@ &:extend(.fotorama-print-background); backface-visibility: hidden; background-image: linear-gradient(to bottom right, rgba(255, 255, 255, 0.25), rgba(64, 64, 64, 0.1)); - border: 1px solid @color-orange-red1; + border: 1px solid @active__color; left: 0; position: absolute; top: 0; diff --git a/lib/web/mage/gallery/module/_extends.less b/lib/web/mage/gallery/module/_extends.less index cfcec77603409..ab38cf40f63d9 100644 --- a/lib/web/mage/gallery/module/_extends.less +++ b/lib/web/mage/gallery/module/_extends.less @@ -42,7 +42,7 @@ .fotorama-focus-overlay { &:after { &:extend(.fotorama-stretch); - background-color: @color-blue2; + background-color: @theme__color__primary-alt; border-radius: inherit; content: ''; } diff --git a/lib/web/mage/menu.js b/lib/web/mage/menu.js index 7096c4ecb7d6d..70501e5cac0c6 100644 --- a/lib/web/mage/menu.js +++ b/lib/web/mage/menu.js @@ -21,7 +21,7 @@ define([ expanded: false, showDelay: 42, hideDelay: 300, - delay: 300, + delay: 0, mediaBreakpoint: '(max-width: 768px)' }, diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index c33e787b29254..01ecaf7cf46c2 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1007,9 +1007,9 @@ ], 'validate-code': [ function (v) { - return $.mage.isEmptyNoTrim(v) || /^[a-z]+[a-z0-9_]+$/.test(v); + return $.mage.isEmptyNoTrim(v) || /^[a-zA-Z]+[a-zA-Z0-9_]+$/.test(v); }, - $.mage.__('Please use only lowercase letters (a-z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter.') //eslint-disable-line max-len + $.mage.__('Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter.') //eslint-disable-line max-len ], 'validate-alphanum': [ function (v) { @@ -1132,10 +1132,10 @@ ovId; if (!result) { - ovId = $(elm).attr('id') + '_value'; + ovId = $('#' + $(elm).attr('id') + '_value'); - if ($(ovId)) { - result = !$.mage.isEmptyNoTrim($(ovId).val()); + if (ovId.length > 0) { + result = !$.mage.isEmptyNoTrim(ovId.val()); } } @@ -1950,6 +1950,9 @@ } if (firstActive.length) { + $('html, body').animate({ + scrollTop: firstActive.offset().top + }); firstActive.focus(); } } diff --git a/phpserver/README.md b/phpserver/README.md index 6bb814fe5f5f2..414ad77ae6b33 100644 --- a/phpserver/README.md +++ b/phpserver/README.md @@ -14,7 +14,7 @@ Without a router script, that is not possible via the php built-in server. ### How to install Magento -Magento's web-based Setup Wizard runs from the `setup` subdirectory, which PHP's built-in web server cannot route. Therefore, you must install Magento using the <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli.html" target="_blank">command line</a>. An example follows: +Magento's web-based Setup Wizard runs from the `setup` subdirectory, which PHP's built-in web server cannot route. Therefore, you must install Magento using the <a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli.html" target="_blank">command line</a>. An example follows: ``` php bin/magento setup:install --base-url=http://127.0.0.1:8082 diff --git a/setup/performance-toolkit/README.md b/setup/performance-toolkit/README.md index bb0a023ab8af1..7bf7a1fbd4721 100644 --- a/setup/performance-toolkit/README.md +++ b/setup/performance-toolkit/README.md @@ -29,7 +29,7 @@ Splitting generation and indexation processes doesn't reduce total processing ti php bin/magento setup:performance:generate-fixtures -s setup/performance-toolkit/profiles/ce/small.xml php bin/magento indexer:reindex -For more information about the available profiles and generating fixtures generation, read [Generate data for performance testing](http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-perf-data.html). +For more information about the available profiles and generating fixtures generation, read [Generate data for performance testing](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-perf-data.html). For run Admin Pool in multithreading mode, please be sure, that: - "Admin Account Sharing" is enabled diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 7c864cce930a3..e7e3087419b55 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -25118,6 +25118,39 @@ catch (java.lang.Exception e) { </ResponseAssertion> <hashTree/> </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Filled Order Page" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_create/index/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">Detected the start of a redirect chain</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert Filled Order Page" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-37823069">Select from existing customer addresses</stringProp> + <stringProp name="-13185722">Submit Order</stringProp> + <stringProp name="-209419315">Items Ordered</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Save Order" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"> @@ -33509,7 +33542,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_entity_id" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_entity_id</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{"entity_id":"(\d+)".+?"parent_id":"${admin_customer_entity_id}"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{"entity_id":"(\d+)".+?"parent_id":"${admin_customer_entity_id}"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33518,7 +33551,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_created_at" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_created_at</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"created_at":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"created_at":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33527,7 +33560,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_updated_at" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_updated_at</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"updated_at":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"updated_at":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33536,7 +33569,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_is_active" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_is_active</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"is_active":"(\d+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"is_active":"(\d+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33545,7 +33578,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_city" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_city</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"city":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"city":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33554,7 +33587,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_country_id" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_country_id</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"country_id":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"country_id":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33563,7 +33596,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_firstname" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_firstname</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"firstname":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"firstname":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33572,7 +33605,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_lastname" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_lastname</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"lastname":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"lastname":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33581,7 +33614,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_postcode" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_postcode</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"postcode":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"postcode":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33590,7 +33623,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_region" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_region</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"region":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"region":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33599,7 +33632,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_region_id" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_region_id</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"region_id":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"region_id":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33608,7 +33641,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address street" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_street</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"street":\["([^"]+)"\]</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"street":\["([^"]+)"\]</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33617,7 +33650,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_telephone" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_telephone</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"telephone":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"telephone":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -33626,7 +33659,7 @@ vars.put("admin_user", adminUser); <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Extract address_customer_id" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> <stringProp name="RegexExtractor.refname">admin_customer_address_customer_id</stringProp> - <stringProp name="RegexExtractor.regex">"address":\{"\d+":{.+?"parent_id":"${admin_customer_entity_id}".+?"customer_id":"([^"]+)"</stringProp> + <stringProp name="RegexExtractor.regex">_address":\{.+?"parent_id":"${admin_customer_entity_id}".+?"customer_id":"([^"]+)"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">1</stringProp> @@ -41079,13 +41112,13 @@ if (totalCount == null) { <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search only" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and filters" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(search: \"Option 1\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> diff --git a/setup/src/Magento/Setup/Controller/LandingInstaller.php b/setup/src/Magento/Setup/Controller/LandingInstaller.php index 45579979b9fcc..49125c47fad9d 100644 --- a/setup/src/Magento/Setup/Controller/LandingInstaller.php +++ b/setup/src/Magento/Setup/Controller/LandingInstaller.php @@ -27,13 +27,15 @@ public function __construct(\Magento\Framework\App\ProductMetadata $productMetad } /** + * Setup index action. + * * @return array|ViewModel */ public function indexAction() { $welcomeMsg = "Welcome to Magento Admin, your online store headquarters.<br>" . "Click 'Agree and Set Up Magento' or read "; - $docRef = "http://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; + $docRef = "https://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; $agreeButtonText = "Agree and Setup Magento"; $view = new ViewModel; $view->setTerminal(true); diff --git a/setup/src/Magento/Setup/Controller/LandingUpdater.php b/setup/src/Magento/Setup/Controller/LandingUpdater.php index e144cc40c37f7..6ae97eec42d23 100644 --- a/setup/src/Magento/Setup/Controller/LandingUpdater.php +++ b/setup/src/Magento/Setup/Controller/LandingUpdater.php @@ -27,15 +27,17 @@ public function __construct(\Magento\Framework\App\ProductMetadata $productMetad } /** + * Updater index action. + * * @return array|ViewModel */ public function indexAction() { $welcomeMsg = "Welcome to Magento Module Manager.<br>" . "Click 'Agree and Update Magento' or read "; - $docRef = "http://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; + $docRef = "https://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; $agreeButtonText = "Agree and Update Magento"; - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); $view->setTemplate('/magento/setup/landing.phtml'); $view->setVariable('version', $this->productMetadata->getVersion()); diff --git a/setup/src/Magento/Setup/Model/SystemPackage.php b/setup/src/Magento/Setup/Model/SystemPackage.php index 232ac1dd342f6..bc5f55c0b128b 100755 --- a/setup/src/Magento/Setup/Model/SystemPackage.php +++ b/setup/src/Magento/Setup/Model/SystemPackage.php @@ -154,6 +154,8 @@ public function getSystemPackageVersions($systemPackageInfo) } /** + * Get installed system packages. + * * @return array * @throws \Exception * @throws \RuntimeException @@ -179,7 +181,7 @@ public function getInstalledSystemPackages() throw new \RuntimeException( 'We\'re sorry, no components are available because you cloned the Magento 2 GitHub repository. ' . 'You must manually update components as discussed in the ' . - '<a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/dev_options.html">' . + '<a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/dev_options.html">' . 'Installation Guide</a>.' ); } @@ -187,6 +189,8 @@ public function getInstalledSystemPackages() } /** + * Sort versions. + * * @param array $enterpriseVersions * @return array */ @@ -241,6 +245,8 @@ private function formatPackages($packages) } /** + * Filter enterprise versions. + * * @param string $currentCE * @param array $enterpriseVersions * @param string $maxVersion diff --git a/setup/view/magento/setup/landing.phtml b/setup/view/magento/setup/landing.phtml index 581be20df937b..645ae6ae9d635 100644 --- a/setup/view/magento/setup/landing.phtml +++ b/setup/view/magento/setup/landing.phtml @@ -15,7 +15,7 @@ <p class="text-welcome"> Welcome to Magento Admin, your online store headquarters. <br> - Click 'Agree and Set Up Magento' or read <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" target="_blank">Getting Started</a> to learn more. + Click 'Agree and Set Up Magento' or read <a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" target="_blank">Getting Started</a> to learn more. </p> <p class="text-terms"> <a href="" ng-click="previousState()">Terms & Agreement</a> diff --git a/setup/view/magento/setup/readiness-check/progress.phtml b/setup/view/magento/setup/readiness-check/progress.phtml index eb9dd0ce9d1aa..d8e519aa8ac74 100755 --- a/setup/view/magento/setup/readiness-check/progress.phtml +++ b/setup/view/magento/setup/readiness-check/progress.phtml @@ -121,7 +121,7 @@ Download and install the updater. </p> <p ng-show="updater.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/updater.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/updater.html" target="_blank">updater application help</a>. </p> </div> @@ -172,7 +172,7 @@ <p ng-show="cronScript.expanded" ng-bind-html="cronScript.updaterErrorMessage"> </p> <p ng-show="cronScript.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/cron.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/cron.html" target="_blank">cron scripts help</a>. </p> </div> @@ -214,7 +214,7 @@ <p ng-show="componentDependency.expanded" ng-bind-html="componentDependency.errorMessage"> </p> <p ng-show="componentDependency.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/component-depend.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/component-depend.html" target="_blank">component dependency help </a>. </p> @@ -336,7 +336,7 @@ </div> <p ng-show="componentDependency.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/trouble/php/tshoot_php-set.html" + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/trouble/php/tshoot_php-set.html" target="_blank">PHP settings check help </a>. </p> @@ -392,7 +392,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -413,7 +413,7 @@ <p> The best way to resolve this is to install the correct missing extensions. The exact fix depends on our server, your host, and other system variables. <br> - Our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. + Our <a href="https://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. </p> <p> For additional assistance, contact your hosting provider. @@ -477,7 +477,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -500,7 +500,7 @@ The best way to resolve this is to allow write permissions for files in the following Magento directories and subdirectories. The exact fix depends on your server, your host, and other system variables. <br> - For help, see our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. + For help, see our <a href="https://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. </p> <ul class="list" ng-show="permissions.expanded" ng-init="showDetails=false"> <li diff --git a/setup/view/magento/setup/start-updater.phtml b/setup/view/magento/setup/start-updater.phtml index b08407b30d4b3..9b2a6b5598aa3 100644 --- a/setup/view/magento/setup/start-updater.phtml +++ b/setup/view/magento/setup/start-updater.phtml @@ -26,7 +26,7 @@ </strong> <div ng-show="type == 'upgrade'"> Before you start upgrade, you can optionally - <a href="http://devdocs.magento.com/guides/v2.1/comp-mgr/trouble/cman/maint-mode.html"> + <a href="https://devdocs.magento.com/guides/v2.1/comp-mgr/trouble/cman/maint-mode.html"> configure a custom maintenance page</a> that informs shoppers the store is offline. (Magento provides a default page.) </div>