diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2839ac5ee9d32..dae954a0970b7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,34 +1,34 @@ # Contributing to Magento 2 code Contributions to the Magento 2 codebase are done using the fork & pull model. -This contribution model has contributors maintaining their own copy of the forked codebase (which can easily be synced with the main copy). The forked repository is then used to submit a request to the base repository to “pull” a set of changes (hence the phrase “pull request”). +This contribution model has contributors maintaining their own copy of the forked codebase (which can easily be synced with the main copy). The forked repository is then used to submit a request to the base repository to “pull” a set of changes. For more information on pull requests please refer to [GitHub Help](https://help.github.com/articles/about-pull-requests/). -Contributions can take the form of new components/features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations or just good suggestions. +Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes or optimizations. -The Magento 2 development team will review all issues and contributions submitted by the community of developers in the first in, first out order. During the review we might require clarifications from the contributor. If there is no response from the contributor for two weeks, the issue is closed. +The Magento 2 development team will review all issues and contributions submitted by the community of developers in the first in, first out order. During the review we might require clarifications from the contributor. If there is no response from the contributor within two weeks, the pull request will be closed. ## Contribution requirements -1. Contributions must adhere to [Magento coding standards](http://devdocs.magento.com/guides/v2.0/coding-standards/bk-coding-standards.html). -2. Pull requests (PRs) must be accompanied by a meaningful description of their purpose. Comprehensive descriptions increase the chances of a pull request to be merged quickly and without additional clarification requests. -3. Commits must be accompanied by meaningful commit messages. -4. PRs which include bug fixing, must be accompanied with step-by-step description of how to reproduce the bug. +1. Contributions must adhere to the [Magento coding standards](https://devdocs.magento.com/guides/v2.2/coding-standards/bk-coding-standards.html). +2. Pull requests (PRs) must be accompanied by a meaningful description of their purpose. Comprehensive descriptions increase the chances of a pull request being merged quickly and without additional clarification requests. +3. Commits must be accompanied by meaningful commit messages. Please see the [Magento Pull Request Template](https://github.com/magento/magento2/blob/2.2-develop/.github/PULL_REQUEST_TEMPLATE.md) for more information. +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 (we will be releasing more information on writing test coverage in the near future). -* Proposed [documentation](http://devdocs.magento.com) update. Documentation contributions can be submitted [here](https://github.com/magento/devdocs). -4. For large features or changes, please [open an issue](https://github.com/magento/magento2/issues) and discuss first. This may prevent duplicate or unnecessary effort, and it may gain you some additional contributors. -5. All automated tests are passed successfully (all builds on [Travis CI](https://travis-ci.org/magento/magento2) must be green). +* 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). +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). ## Contribution process -If you are a new GitHub user, we recommend that you create your own [free github account](https://github.com/signup/free). By doing that, you will be able to collaborate with the Magento 2 development team, “fork” the Magento 2 project and be able to easily send “pull requests”. +If you are a new GitHub user, we recommend that you create your own [free github account](https://github.com/signup/free). This will allow you to collaborate with the Magento 2 development team, fork the Magento 2 project and send pull requests. 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 [Fork a repository instructions](http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow [Create a pull request instructions](http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#pull_request). -5. Once your contribution is received, Magento 2 development team will review the contribution and collaborate with you as needed to improve the quality of the contribution. +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). +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/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 3ac68076d4353..2b1720ccaabae 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,24 +1,36 @@ - - + - + Please also have a look at our guidelines article before adding a new issue https://github.com/magento/magento2/wiki/Issue-reporting-guidelines +--> + +### Preconditions (*) + 1. 2. -### Steps to reproduce - +### Steps to reproduce (*) + 1. 2. 3. -### Expected result +### Expected result (*) -1. +1. [Screenshots, logs or description] -### Actual result +### Actual result (*) -1. [Screenshot, logs] - - +1. [Screenshots, logs or description] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..33a6ef02ace11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Technical issue with the Magento 2 core components + +--- + + + +### Preconditions (*) + +1. +2. + +### Steps to reproduce (*) + +1. +2. + +### Expected result (*) + +1. [Screenshots, logs or description] +2. + +### Actual result (*) + +1. [Screenshots, logs or description] +2. diff --git a/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md new file mode 100644 index 0000000000000..423d4818fb31c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/developer-experience-issue.md @@ -0,0 +1,19 @@ +--- +name: Developer experience issue +about: Issues related to customization, extensibility, modularity + +--- + + + +### Summary (*) + + +### Examples (*) + + +### Proposed solution + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..f64185773cab4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Please consider reporting directly to https://github.com/magento/community-features + +--- + + + +### Description (*) + + +### Expected behavior (*) + + +### Benefits + + +### Additional information + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d1f01ba9f2640..f191bd9aaba67 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,37 @@ - + -### Description - + + +### Description (*) + ### Fixed Issues (if relevant) - + 1. magento/magento2#: Issue title 2. ... -### Manual testing scenarios - +### Manual testing scenarios (*) + 1. ... 2. ... -### Contribution checklist +### Contribution checklist (*) - [ ] Pull request has a meaningful description of its purpose - [ ] All commits are accompanied by meaningful commit messages - [ ] All new or changed code is covered with unit/integration tests (if applicable) diff --git a/.htaccess b/.htaccess index 6247830fa8d14..d22b5a1395cae 100644 --- a/.htaccess +++ b/.htaccess @@ -355,6 +355,15 @@ Require all denied + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + # For 404s and 403s that aren't handled by the application, show plain 404 response ErrorDocument 404 /pub/errors/404.php diff --git a/.htaccess.sample b/.htaccess.sample index 8e6e702ced716..c9ddff2cca4cf 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -111,7 +111,8 @@ ############################################ ## enable rewrites - Options +FollowSymLinks + # The following line has better security but add some performance overhead - see https://httpd.apache.org/docs/2.4/en/misc/perf-tuning.html + Options -FollowSymLinks +SymLinksIfOwnerMatch RewriteEngine on ############################################ @@ -331,6 +332,15 @@ Require all denied + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + # For 404s and 403s that aren't handled by the application, show plain 404 response ErrorDocument 404 /pub/errors/404.php diff --git a/.php_cs.dist b/.php_cs.dist index 0f254c63283bd..84a5f88bf4355 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -5,9 +5,9 @@ */ /** - * Pre-commit hook installation: - * vendor/bin/static-review.php hook:install dev/tools/Magento/Tools/StaticReview/pre-commit .git/hooks/pre-commit + * PHP Coding Standards fixer configuration */ + $finder = PhpCsFixer\Finder::create() ->name('*.phtml') ->exclude('dev/tests/functional/generated') diff --git a/.travis.yml b/.travis.yml index dcd00f39bb810..cc730ca5a2cd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,15 +11,18 @@ addons: firefox: "46.0" hosts: - magento2.travis +services: + - rabbitmq + - elasticsearch language: php php: - - 7.0 - 7.1 + - 7.2 env: global: - COMPOSER_BIN_DIR=~/bin - INTEGRATION_SETS=3 - - NODE_JS_VERSION=6 + - NODE_JS_VERSION=8 - MAGENTO_HOST_NAME="magento2.travis" matrix: - TEST_SUITE=unit @@ -32,13 +35,13 @@ env: - TEST_SUITE=functional matrix: exclude: - - php: 7.0 + - php: 7.1 env: TEST_SUITE=static - - php: 7.0 + - php: 7.1 env: TEST_SUITE=js GRUNT_COMMAND=spec - - php: 7.0 + - php: 7.1 env: TEST_SUITE=js GRUNT_COMMAND=static - - php: 7.0 + - php: 7.1 env: TEST_SUITE=functional cache: apt: true @@ -47,7 +50,9 @@ cache: - $HOME/.nvm - $HOME/node_modules - $HOME/yarn.lock -before_install: ./dev/travis/before_install.sh +before_install: + - curl -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.3.0/elasticsearch-2.3.0.deb && sudo dpkg -i --force-confnew elasticsearch-2.3.0.deb && sudo service elasticsearch restart + - ./dev/travis/before_install.sh install: composer install --no-interaction before_script: ./dev/travis/before_script.sh script: diff --git a/CHANGELOG.md b/CHANGELOG.md index ef841ec0337f2..cc722b6d61b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +2.3.0 +============= +To get detailed information about changes in Magento 2.3.0, see the [Release Notes](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html) + 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") @@ -1977,7 +1981,7 @@ Tests: * [#686](https://github.com/magento/magento2/issues/686) -- Product save validation errors in the admin don't hide the overlay * [#702](https://github.com/magento/magento2/issues/702) -- Base table or view not found * [#652](https://github.com/magento/magento2/issues/652) -- Multishipping checkout not to change the Billing address js issue - * [#648](https://github.com/magento/magento2/issues/648) -- An equal (=) sign in the hash of the product page to to break the tabs functionality + * [#648](https://github.com/magento/magento2/issues/648) -- An equal (=) sign in the hash of the product page to break the tabs functionality * Service Contracts: * Refactored usage of new API of the Customer module * Implemented Service Contracts for the Sales module diff --git a/COPYING.txt b/COPYING.txt index d2cbcd01539dd..040bdd5f3ce72 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,4 +1,4 @@ -Copyright © 2013-2017 Magento, Inc. +Copyright © 2013-present Magento, Inc. Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license diff --git a/README.md b/README.md index 9b1aa1b7b3e28..c292f1100f336 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ -[![Build Status](https://travis-ci.org/magento/magento2.svg?branch=develop)](https://travis-ci.org/magento/magento2) +[![Build Status](https://travis-ci.org/magento/magento2.svg?branch=2.3-develop)](https://travis-ci.org/magento/magento2) +[![Open Source Helpers](https://www.codetriage.com/magento/magento2/badges/users.svg)](https://www.codetriage.com/magento/magento2) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magento/magento2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/magento-2/localized.png)](https://crowdin.com/project/magento-2) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/magento-2/localized.svg)](https://crowdin.com/project/magento-2)

Welcome

-Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting edge, feature-rich eCommerce solution that gets results. +Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting-edge, feature-rich eCommerce solution that gets results. ## Magento system requirements -[Magento system requirements](http://devdocs.magento.com/magento-system-requirements.html) +[Magento system requirements](http://devdocs.magento.com/guides/v2.3/install-gde/system-requirements2.html). ## Install Magento -To install Magento, see either: -* [Magento DevBox](https://magento.com/tech-resources/download), the easiest way to get started with Magento. -* [Installation guide](http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html) +* [Installation guide](http://devdocs.magento.com/guides/v2.3/install-gde/bk-install-guide.html).

Contributing to the Magento 2 code base

Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. @@ -22,34 +21,37 @@ To learn about issues, click [here][2]. To open an issue, click [here][3]. To suggest documentation improvements, click [here][4]. -[1]: -[2]: +[1]: +[2]: [3]: [4]: -

Labels applied by the Magento team

+

Community Maintainers

+The members of this team have been recognized for their outstanding commitment to maintaining and improving Magento. Magento has granted them permission to accept, merge, and reject pull requests, as well as review issues, and thanks these Community Maintainers for their valuable contributions. + + + + -| Label | Description | -| ------------- |-------------| -| ![DOC](http://devdocs.magento.com/common/images/github_DOC.png) | Affects Documentation domain. | -| ![PROD](http://devdocs.magento.com/common/images/github_PROD.png) | Affects the Product team (mostly feature requests or business logic change). | -| ![TECH](http://devdocs.magento.com/common/images/github_TECH.png) | Affects Architect Group (mostly to make decisions around technology changes). | -| ![accept](http://devdocs.magento.com/common/images/github_accept.png) | The pull request has been accepted and will be merged into mainline code. | -| ![reject](http://devdocs.magento.com/common/images/github_reject.png) | The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution. | -| ![bug report](http://devdocs.magento.com/common/images/github_bug.png) | The Magento Team has confirmed that this issue contains the minimum required information to reproduce. | -| ![acknowledged](http://devdocs.magento.com/common/images/gitHub_acknowledged.png) | The Magento Team has validated the issue and an internal ticket has been created. | -| ![acknowledged](http://devdocs.magento.com/common/images/github_inProgress.png) | The internal ticket is currently in progress, fix is scheduled to be delivered. | -| ![acknowledged](http://devdocs.magento.com/common/images/github_needsUpdate.png) | The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request. | +

Top Contributors

+Magento is thankful for any contribution that can improve our code base, documentation or increase test coverage. We always recognize our most active members, as their contributions are the foundation of the Magento Open Source platform. + + + + +

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.

Reporting security issues

-To report security vulnerabilities in Magento software or web sites, please e-mail security@magento.com. Please do not report security issues using GitHub. Be sure to encrypt your e-mail with our encryption key if it includes sensitive information. Learn more about reporting security issues here. +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. Stay up-to-date on the latest security news and patches for Magento by signing up for Security Alert Notifications.

License

-Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license +Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license. http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) Please see LICENSE.txt for the full text of the OSL 3.0 license or contact license@magentocommerce.com for a copy. diff --git a/app/bootstrap.php b/app/bootstrap.php index 6701a9f4dd51e..8e901cac9bfb8 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -14,12 +14,12 @@ if (!defined('PHP_VERSION_ID') || !(PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { if (PHP_SAPI == 'cli') { echo 'Magento supports 7.0.2, 7.0.4, and 7.0.6 or later. ' . - 'Please read http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html'; + 'Please read http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html'; } else { echo <<

Magento supports PHP 7.0.2, 7.0.4, and 7.0.6 or later. Please read - + Magento System Requirements. HTML; @@ -31,8 +31,6 @@ // Sets default autoload mappings, may be overridden in Bootstrap::create \Magento\Framework\App\Bootstrap::populateAutoloader(BP, []); -require_once BP . '/app/functions.php'; - /* Custom umask value may be provided in optional mage_umask file in root */ $umaskFile = BP . '/magento_umask'; $mask = file_exists($umaskFile) ? octdec(file_get_contents($umaskFile)) : 002; @@ -49,12 +47,21 @@ unset($_SERVER['ORIG_PATH_INFO']); } -if (!empty($_SERVER['MAGE_PROFILER']) +if ( + (!empty($_SERVER['MAGE_PROFILER']) || file_exists(BP . '/var/profiler.flag')) && isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false ) { - \Magento\Framework\Profiler::applyConfig( - $_SERVER['MAGE_PROFILER'], + $profilerConfig = isset($_SERVER['MAGE_PROFILER']) && strlen($_SERVER['MAGE_PROFILER']) + ? $_SERVER['MAGE_PROFILER'] + : trim(file_get_contents(BP . '/var/profiler.flag')); + + if ($profilerConfig) { + $profilerConfig = json_decode($profilerConfig, true) ?: $profilerConfig; + } + + Magento\Framework\Profiler::applyConfig( + $profilerConfig, BP, !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' ); diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index e95d68663bf04..b950f5583e599 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -16,24 +16,34 @@ class Messages extends \Magento\Backend\Block\Template /** * @var \Magento\Framework\Json\Helper\Data + * @deprecated */ protected $jsonHelper; + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $serializer; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $messages * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param array $data + * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $messages, \Magento\Framework\Json\Helper\Data $jsonHelper, - array $data = [] + array $data = [], + \Magento\Framework\Serialize\Serializer\Json $serializer = null ) { $this->jsonHelper = $jsonHelper; parent::__construct($context, $data); $this->_messages = $messages; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); } /** @@ -117,7 +127,7 @@ protected function _getMessagesUrl() */ public function getSystemMessageDialogJson() { - return $this->jsonHelper->jsonEncode( + return $this->serializer->serialize( [ 'systemMessageDialog' => [ 'buttons' => [], diff --git a/app/code/Magento/AdminNotification/Block/System/Messages/UnreadMessagePopup.php b/app/code/Magento/AdminNotification/Block/System/Messages/UnreadMessagePopup.php index 7ea0062581467..2d4c7f279f707 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages/UnreadMessagePopup.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages/UnreadMessagePopup.php @@ -77,9 +77,8 @@ public function getPopupTitle() $messageCount = count($this->_messages->getUnread()); if ($messageCount > 1) { return __('You have %1 new system messages', $messageCount); - } else { - return __('You have %1 new system message', $messageCount); } + return __('You have %1 new system message', $messageCount); } /** diff --git a/app/code/Magento/AdminNotification/Block/Window.php b/app/code/Magento/AdminNotification/Block/Window.php index b80e12a8674db..9563626ee2577 100644 --- a/app/code/Magento/AdminNotification/Block/Window.php +++ b/app/code/Magento/AdminNotification/Block/Window.php @@ -98,10 +98,9 @@ protected function _getLatestItem() { if ($this->_latestItem == null) { $items = array_values($this->_criticalCollection->getItems()); + $this->_latestItem = false; if (count($items)) { $this->_latestItem = $items[0]; - } else { - $this->_latestItem = false; } } return $this->_latestItem; diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Index.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Index.php index 125dba405b108..22eb3b30722f4 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Index.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; -class Index extends \Magento\AdminNotification\Controller\Adminhtml\Notification +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\AdminNotification\Controller\Adminhtml\Notification implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index 79f69ab5da88d..6b5e0681139cf 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -28,11 +28,11 @@ public function execute() )->markAsRead( $notificationId ); - $this->messageManager->addSuccess(__('The message has been marked as Read.')); + $this->messageManager->addSuccessMessage(__('The message has been marked as Read.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __("We couldn't mark the notification as Read because of an error.") ); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index 9e61b8ff4b83c..9ae4a7cdac0b9 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -23,7 +23,7 @@ public function execute() { $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { - $this->messageManager->addError(__('Please select messages.')); + $this->messageManager->addErrorMessage(__('Please select messages.')); } else { try { foreach ($ids as $id) { @@ -32,13 +32,13 @@ public function execute() $model->setIsRead(1)->save(); } } - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('A total of %1 record(s) have been marked as Read.', count($ids)) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __("We couldn't mark the notification as Read because of an error.") ); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index 6c0dfd1db7d16..06659b8452cab 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -23,7 +23,7 @@ public function execute() { $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { - $this->messageManager->addError(__('Please select messages.')); + $this->messageManager->addErrorMessage(__('Please select messages.')); } else { try { foreach ($ids as $id) { @@ -32,13 +32,16 @@ public function execute() $model->setIsRemove(1)->save(); } } - $this->messageManager->addSuccess(__('Total of %1 record(s) have been removed.', count($ids))); + $this->messageManager->addSuccessMessage(__('Total of %1 record(s) have been removed.', count($ids))); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("We couldn't remove the messages because of an error.")); + $this->messageManager->addExceptionMessage( + $e, + __("We couldn't remove the messages because of an error.") + ); } } - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); + $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index 17f911339cb61..f0724a9587c50 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -31,11 +31,14 @@ public function execute() try { $model->setIsRemove(1)->save(); - $this->messageManager->addSuccess(__('The message has been removed.')); + $this->messageManager->addSuccessMessage(__('The message has been removed.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("We couldn't remove the messages because of an error.")); + $this->messageManager->addExceptionMessage( + $e, + __("We couldn't remove the messages because of an error.") + ); } $this->_redirect('adminhtml/*/'); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php index c332440276083..d58a7ec31f77d 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php @@ -6,6 +6,8 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\System\Message; +use Magento\Framework\Controller\ResultFactory; + class ListAction extends \Magento\Backend\App\AbstractAction { /** @@ -15,6 +17,7 @@ class ListAction extends \Magento\Backend\App\AbstractAction /** * @var \Magento\Framework\Json\Helper\Data + * @deprecated */ protected $jsonHelper; @@ -41,7 +44,7 @@ public function __construct( } /** - * @return void + * @return \Magento\Framework\Controller\Result\Json */ public function execute() { @@ -59,10 +62,15 @@ public function execute() if (empty($result)) { $result[] = [ 'severity' => (string)\Magento\Framework\Notification\MessageInterface::SEVERITY_NOTICE, - 'text' => 'You have viewed and resolved all recent system notices. ' - . 'Please refresh the web page to clear the notice alert.', + 'text' => __( + 'You have viewed and resolved all recent system notices. ' + . 'Please refresh the web page to clear the notice alert.' + ) ]; } - $this->getResponse()->representJson($this->jsonHelper->jsonEncode($result)); + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $resultJson->setData($result); + return $resultJson; } } diff --git a/app/code/Magento/AdminNotification/Model/Feed.php b/app/code/Magento/AdminNotification/Model/Feed.php index 1766425fb19b1..d3b0b8501c864 100644 --- a/app/code/Magento/AdminNotification/Model/Feed.php +++ b/app/code/Magento/AdminNotification/Model/Feed.php @@ -214,9 +214,6 @@ public function getFeedData() ); $curl->write(\Zend_Http_Client::GET, $this->getFeedUrl(), '1.0'); $data = $curl->read(); - if ($data === false) { - return false; - } $data = preg_split('/^\r?$/m', $data, 2); $data = trim($data[1]); $curl->close(); diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/System/Message.php b/app/code/Magento/AdminNotification/Model/ResourceModel/System/Message.php index c7e9d348f3aca..9d830274b004e 100644 --- a/app/code/Magento/AdminNotification/Model/ResourceModel/System/Message.php +++ b/app/code/Magento/AdminNotification/Model/ResourceModel/System/Message.php @@ -12,7 +12,7 @@ class Message extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** - * Flag that notifies whether Primary key of table is auto-incremeted + * Flag that notifies whether Primary key of table is auto-incremented * * @var bool */ diff --git a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php index 3275de2a82fb7..24ef712c0f61f 100644 --- a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php +++ b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php @@ -37,7 +37,7 @@ public function __construct( } /** - * Predispath admin action controller + * Predispatch admin action controller * * @param \Magento\Framework\Event\Observer $observer * @return void diff --git a/app/code/Magento/AdminNotification/Setup/InstallSchema.php b/app/code/Magento/AdminNotification/Setup/InstallSchema.php deleted file mode 100644 index 081be974dc809..0000000000000 --- a/app/code/Magento/AdminNotification/Setup/InstallSchema.php +++ /dev/null @@ -1,124 +0,0 @@ -startSetup(); - /** - * Create table 'adminnotification_inbox' - */ - $table = $installer->getConnection()->newTable( - $installer->getTable('adminnotification_inbox') - )->addColumn( - 'notification_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], - 'Notification id' - )->addColumn( - 'severity', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Problem type' - )->addColumn( - 'date_added', - \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, - null, - ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], - 'Create date' - )->addColumn( - 'title', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 255, - ['nullable' => false], - 'Title' - )->addColumn( - 'description', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - '64k', - [], - 'Description' - )->addColumn( - 'url', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 255, - [], - 'Url' - )->addColumn( - 'is_read', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Flag if notification read' - )->addColumn( - 'is_remove', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Flag if notification might be removed' - )->addIndex( - $installer->getIdxName('adminnotification_inbox', ['severity']), - ['severity'] - )->addIndex( - $installer->getIdxName('adminnotification_inbox', ['is_read']), - ['is_read'] - )->addIndex( - $installer->getIdxName('adminnotification_inbox', ['is_remove']), - ['is_remove'] - )->setComment( - 'Adminnotification Inbox' - ); - $installer->getConnection()->createTable($table); - - /** - * Create table 'admin_system_messages' - */ - $table = $installer->getConnection()->newTable( - $installer->getTable('admin_system_messages') - )->addColumn( - 'identity', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 100, - ['nullable' => false, 'primary' => true], - 'Message id' - )->addColumn( - 'severity', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Problem type' - )->addColumn( - 'created_at', - \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, - null, - ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], - 'Create date' - )->setComment( - 'Admin System Messages' - ); - $installer->getConnection()->createTable($table); - - $installer->endSetup(); - } -} diff --git a/dev/tests/acceptance/LICENSE.txt b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/LICENSE.txt rename to app/code/Magento/AdminNotification/Test/Mftf/LICENSE.txt diff --git a/app/design/frontend/Magento/rush/LICENSE_AFL.txt b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from app/design/frontend/Magento/rush/LICENSE_AFL.txt rename to app/code/Magento/AdminNotification/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/AdminNotification/Test/Mftf/README.md b/app/code/Magento/AdminNotification/Test/Mftf/README.md new file mode 100644 index 0000000000000..33f88ba74200a --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Admin Notification Functional Tests + +The Functional Test Module for **Magento Admin Notification** module. diff --git a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/CacheOutdatedTest.php b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/CacheOutdatedTest.php index 2fbfc43aa8775..f49911c3e7a93 100644 --- a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/CacheOutdatedTest.php +++ b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/CacheOutdatedTest.php @@ -62,6 +62,9 @@ public function testGetIdentity($expectedSum, $cacheTypes) $this->assertEquals($expectedSum, $this->_messageModel->getIdentity()); } + /** + * @return array + */ public function getIdentityDataProvider() { $cacheTypeMock1 = $this->createPartialMock(\stdClass::class, ['getCacheType']); @@ -95,6 +98,9 @@ public function testIsDisplayed($expected, $allowed, $cacheTypes) $this->assertEquals($expected, $this->_messageModel->isDisplayed()); } + /** + * @return array + */ public function isDisplayedDataProvider() { $cacheTypesMock = $this->createPartialMock(\stdClass::class, ['getCacheType']); diff --git a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/Media/Synchronization/ErrorTest.php b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/Media/Synchronization/ErrorTest.php index 2c259db868851..b490efd8e9683 100644 --- a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/Media/Synchronization/ErrorTest.php +++ b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/Media/Synchronization/ErrorTest.php @@ -72,6 +72,9 @@ public function testIsDisplayed($expectedFirstRun, $data) $this->assertEquals($expectedFirstRun, $model->isDisplayed()); } + /** + * @return array + */ public function isDisplayedDataProvider() { return [ diff --git a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/SecurityTest.php b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/SecurityTest.php index 1e71570a5e30b..c6f61fee862ba 100644 --- a/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/SecurityTest.php +++ b/app/code/Magento/AdminNotification/Test/Unit/Model/System/Message/SecurityTest.php @@ -76,6 +76,9 @@ public function testIsDisplayed($expectedResult, $cached, $response) $this->assertEquals($expectedResult, $this->_messageModel->isDisplayed()); } + /** + * @return array + */ public function isDisplayedDataProvider() { return [ diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index b8dba6f899645..e5cf487908cd7 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -5,16 +5,15 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "php": "~7.1.3||~7.2.0", "lib-libxml": "*", - "magento/framework": "100.3.*", - "magento/module-backend": "100.3.*", - "magento/module-media-storage": "100.3.*", - "magento/module-store": "100.3.*", - "magento/module-ui": "100.3.*" + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-media-storage": "*", + "magento/module-store": "*", + "magento/module-ui": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml index fbed5c0960b73..04d700b9f90ce 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml @@ -7,6 +7,6 @@ -->

- + diff --git a/app/code/Magento/AdminNotification/etc/db_schema.xml b/app/code/Magento/AdminNotification/etc/db_schema.xml new file mode 100644 index 0000000000000..35e6045b607d1 --- /dev/null +++ b/app/code/Magento/AdminNotification/etc/db_schema.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+
diff --git a/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json b/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..b068ffffe9219 --- /dev/null +++ b/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json @@ -0,0 +1,32 @@ +{ + "adminnotification_inbox": { + "column": { + "notification_id": true, + "severity": true, + "date_added": true, + "title": true, + "description": true, + "url": true, + "is_read": true, + "is_remove": true + }, + "index": { + "ADMINNOTIFICATION_INBOX_SEVERITY": true, + "ADMINNOTIFICATION_INBOX_IS_READ": true, + "ADMINNOTIFICATION_INBOX_IS_REMOVE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "admin_system_messages": { + "column": { + "identity": true, + "severity": true, + "created_at": true + }, + "constraint": { + "PRIMARY": true + } + } +} \ No newline at end of file diff --git a/app/code/Magento/AdminNotification/etc/module.xml b/app/code/Magento/AdminNotification/etc/module.xml index 8a792ee8453ce..607ecbde10a26 100644 --- a/app/code/Magento/AdminNotification/etc/module.xml +++ b/app/code/Magento/AdminNotification/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/AdminNotification/i18n/en_US.csv b/app/code/Magento/AdminNotification/i18n/en_US.csv index 16c5abb9db0d2..db5a4c9254814 100644 --- a/app/code/Magento/AdminNotification/i18n/en_US.csv +++ b/app/code/Magento/AdminNotification/i18n/en_US.csv @@ -48,3 +48,4 @@ Severity,Severity "Date Added","Date Added" Message,Message Actions,Actions +"You have viewed and resolved all recent system notices. Please refresh the web page to clear the notice alert.","You have viewed and resolved all recent system notices. Please refresh the web page to clear the notice alert." diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml index a97293547e132..0448daaf17644 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml @@ -19,20 +19,12 @@ - + \ No newline at end of file diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js new file mode 100644 index 0000000000000..39c61d6e07d29 --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js @@ -0,0 +1,26 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Ui/js/modal/modal' +], function ($, modal) { + 'use strict'; + + return function (data, element) { + + if (modal.modal) { + modal.modal.html($(element).html()); + } else { + modal.modal = $(element).modal({ + modalClass: data.class, + type: 'popup', + buttons: [] + }); + } + + modal.modal.modal('openModal'); + }; +}); diff --git a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php index 02413a1899cd7..d78266ab75311 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -5,12 +5,14 @@ */ namespace Magento\AdvancedPricingImportExport\Controller\Adminhtml\Export; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Controller\ResultFactory; use Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing as ExportAdvancedPricing; use Magento\Catalog\Model\Product as CatalogProduct; -class GetFilter extends ExportController +class GetFilter extends ExportController implements HttpGetActionInterface, HttpPostActionInterface { /** * Get grid-filter of entity attributes action. @@ -37,10 +39,10 @@ public function execute() ); return $resultLayout; } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } else { - $this->messageManager->addError(__('Please correct the data sent.')); + $this->messageManager->addErrorMessage(__('Please correct the data sent.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index 7ddd5e3bb2a36..fda6ae9530135 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -5,6 +5,7 @@ */ namespace Magento\AdvancedPricingImportExport\Model\Export; +use Magento\ImportExport\Model\Export; use Magento\Store\Model\Store; use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing as ImportAdvancedPricing; @@ -79,6 +80,11 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product ImportAdvancedPricing::COL_TIER_PRICE_TYPE => '' ]; + /** + * @var string[] + */ + private $websiteCodesMap = []; + /** * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Eav\Model\Config $config @@ -98,7 +104,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product * @param \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer * @param ImportProduct\StoreResolver $storeResolver * @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository - * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -187,6 +192,7 @@ protected function initTypeModels() * Export process * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function export() { @@ -255,36 +261,131 @@ public function filterAttributeCollection(\Magento\Eav\Model\ResourceModel\Entit */ protected function getExportData() { + if ($this->_passTierPrice) { + return []; + } + $exportData = []; try { - $rawData = $this->collectRawData(); - $productIds = array_keys($rawData); - if (isset($productIds)) { - if (!$this->_passTierPrice) { - $exportData = array_merge( - $exportData, - $this->getTierPrices($productIds, ImportAdvancedPricing::TABLE_TIER_PRICE) - ); + $productsByStores = $this->loadCollection(); + if (!empty($productsByStores)) { + $linkField = $this->getProductEntityLinkField(); + $productLinkIds = []; + + foreach ($productsByStores as $product) { + $productLinkIds[array_pop($product)[$linkField]] = true; + } + $productLinkIds = array_keys($productLinkIds); + $tierPricesData = $this->fetchTierPrices($productLinkIds); + $exportData = $this->prepareExportData( + $productsByStores, + $tierPricesData + ); + if (!empty($exportData)) { + asort($exportData); } } - if ($exportData) { - $exportData = $this->correctExportData($exportData); - } - if (isset($exportData)) { - asort($exportData); - } - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->_logger->critical($e); } + return $exportData; } + /** + * Creating export-formatted row from tier price. + * + * @param array $tierPriceData Tier price information. + * + * @return array Formatted for export tier price information. + */ + private function createExportRow(array $tierPriceData): array + { + //List of columns to display in export row. + $exportRow = $this->templateExportData; + + foreach (array_keys($exportRow) as $keyTemplate) { + if (array_key_exists($keyTemplate, $tierPriceData)) { + if (in_array($keyTemplate, $this->_priceWebsite)) { + //If it's website column then getting website code. + $exportRow[$keyTemplate] = $this->_getWebsiteCode( + $tierPriceData[$keyTemplate] + ); + } elseif (in_array($keyTemplate, $this->_priceCustomerGroup)) { + //If it's customer group column then getting customer + //group name by ID. + $exportRow[$keyTemplate] = $this->_getCustomerGroupById( + $tierPriceData[$keyTemplate], + $tierPriceData[ImportAdvancedPricing::VALUE_ALL_GROUPS] + ); + unset($exportRow[ImportAdvancedPricing::VALUE_ALL_GROUPS]); + } elseif ($keyTemplate + === ImportAdvancedPricing::COL_TIER_PRICE + ) { + //If it's price column then getting value and type + //of tier price. + $exportRow[$keyTemplate] + = $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + ? $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + : $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE]; + $exportRow[ImportAdvancedPricing::COL_TIER_PRICE_TYPE] + = $this->tierPriceTypeValue($tierPriceData); + } else { + //Any other column just goes as is. + $exportRow[$keyTemplate] = $tierPriceData[$keyTemplate]; + } + } + } + + return $exportRow; + } + + /** + * Prepare data for export. + * + * @param array $productsData Products to export. + * @param array $tierPricesData Their tier prices. + * + * @return array Export rows to display. + */ + private function prepareExportData( + array $productsData, + array $tierPricesData + ): array { + //Assigning SKUs to tier prices data. + $productLinkIdToSkuMap = []; + foreach ($productsData as $productData) { + $productLinkIdToSkuMap[$productData[Store::DEFAULT_STORE_ID][$this->getProductEntityLinkField()]] + = $productData[Store::DEFAULT_STORE_ID]['sku']; + } + + //Adding products' SKUs to tier price data. + $linkedTierPricesData = []; + foreach ($tierPricesData as $tierPriceData) { + $sku = $productLinkIdToSkuMap[$tierPriceData['product_link_id']]; + $linkedTierPricesData[] = array_merge( + $tierPriceData, + [ImportAdvancedPricing::COL_SKU => $sku] + ); + } + + //Formatting data for export. + $customExportData = []; + foreach ($linkedTierPricesData as $row) { + $customExportData[] = $this->createExportRow($row); + } + + return $customExportData; + } + /** * Correct export data. * * @param array $exportData * @return array * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @deprecated + * @see prepareExportData */ protected function correctExportData($exportData) { @@ -327,16 +428,83 @@ protected function correctExportData($exportData) /** * Check type for tier price. * - * @param string $tierPricePercentage + * @param array $tierPriceData * @return string */ - private function tierPriceTypeValue($tierPricePercentage) + private function tierPriceTypeValue(array $tierPriceData): string { - return $tierPricePercentage + return $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] ? ImportAdvancedPricing::TIER_PRICE_TYPE_PERCENT : ImportAdvancedPricing::TIER_PRICE_TYPE_FIXED; } + /** + * Load tier prices for given products. + * + * @param string[] $productIds Link IDs of products to find tier prices for. + * + * @return array Tier prices data. + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function fetchTierPrices(array $productIds): array + { + if (empty($productIds)) { + throw new \InvalidArgumentException( + 'Can only load tier prices for specific products' + ); + } + + $pricesTable = ImportAdvancedPricing::TABLE_TIER_PRICE; + $exportFilter = null; + $priceFromFilter = null; + $priceToFilter = null; + if (isset($this->_parameters[Export::FILTER_ELEMENT_GROUP])) { + $exportFilter = $this->_parameters[Export::FILTER_ELEMENT_GROUP]; + } + $productEntityLinkField = $this->getProductEntityLinkField(); + $selectFields = [ + ImportAdvancedPricing::COL_TIER_PRICE_WEBSITE => 'ap.website_id', + ImportAdvancedPricing::VALUE_ALL_GROUPS => 'ap.all_groups', + ImportAdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'ap.customer_group_id', + ImportAdvancedPricing::COL_TIER_PRICE_QTY => 'ap.qty', + ImportAdvancedPricing::COL_TIER_PRICE => 'ap.value', + ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE => 'ap.percentage_value', + 'product_link_id' => 'ap.' .$productEntityLinkField, + ]; + if ($exportFilter && array_key_exists('tier_price', $exportFilter)) { + if (!empty($exportFilter['tier_price'][0])) { + $priceFromFilter = $exportFilter['tier_price'][0]; + } + if (!empty($exportFilter['tier_price'][1])) { + $priceToFilter = $exportFilter['tier_price'][1]; + } + } + + $select = $this->_connection->select() + ->from( + ['ap' => $this->_resource->getTableName($pricesTable)], + $selectFields + ) + ->where( + 'ap.'.$productEntityLinkField.' IN (?)', + $productIds + ); + + if ($priceFromFilter !== null) { + $select->where('ap.value >= ?', $priceFromFilter); + } + if ($priceToFilter !== null) { + $select->where('ap.value <= ?', $priceToFilter); + } + if ($priceFromFilter || $priceToFilter) { + $select->orWhere('ap.percentage_value IS NOT NULL'); + } + + return $this->_connection->fetchAll($select); + } + /** * Get tier prices. * @@ -345,6 +513,8 @@ private function tierPriceTypeValue($tierPricePercentage) * @return array|bool * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @deprecated + * @see fetchTierPrices */ protected function getTierPrices(array $listSku, $table) { @@ -413,41 +583,52 @@ protected function getTierPrices(array $listSku, $table) } /** - * Get Website code + * Get Website code. * * @param int $websiteId * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _getWebsiteCode($websiteId) + protected function _getWebsiteCode(int $websiteId): string { - $storeName = ($websiteId == 0) - ? ImportAdvancedPricing::VALUE_ALL_WEBSITES - : $this->_storeManager->getWebsite($websiteId)->getCode(); - $currencyCode = ''; - if ($websiteId == 0) { - $currencyCode = $this->_storeManager->getWebsite($websiteId)->getBaseCurrencyCode(); - } - if ($storeName && $currencyCode) { - return $storeName . ' [' . $currencyCode . ']'; - } else { - return $storeName; + if (!array_key_exists($websiteId, $this->websiteCodesMap)) { + $storeName = ($websiteId == 0) + ? ImportAdvancedPricing::VALUE_ALL_WEBSITES + : $this->_storeManager->getWebsite($websiteId)->getCode(); + $currencyCode = ''; + if ($websiteId == 0) { + $currencyCode = $this->_storeManager->getWebsite($websiteId) + ->getBaseCurrencyCode(); + } + + if ($storeName && $currencyCode) { + $code = $storeName.' ['.$currencyCode.']'; + } else { + $code = $storeName; + } + $this->websiteCodesMap[$websiteId] = $code; } + + return $this->websiteCodesMap[$websiteId]; } /** - * Get Customer Group By Id + * Get Customer Group By Id. * - * @param int $customerGroupId - * @param null $allGroups + * @param int $groupId + * @param int $allGroups * @return string + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ - protected function _getCustomerGroupById($customerGroupId, $allGroups = null) - { - if ($allGroups) { + protected function _getCustomerGroupById( + int $groupId, + int $allGroups = 0 + ): string { + if ($allGroups !== 0) { return ImportAdvancedPricing::VALUE_ALL_GROUPS; - } else { - return $this->_groupRepository->getById($customerGroupId)->getCode(); } + return $this->_groupRepository->getById($groupId)->getCode(); } /** diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 23829d3725119..2e17e734b1e60 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -8,7 +8,6 @@ use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; -use Magento\Framework\App\ResourceConnection; /** * Class AdvancedPricing @@ -394,7 +393,7 @@ protected function saveAndReplaceAdvancedPrices() ? $rowData[self::COL_TIER_PRICE] : 0, 'percentage_value' => $rowData[self::COL_TIER_PRICE_TYPE] === self::TIER_PRICE_TYPE_PERCENT ? $rowData[self::COL_TIER_PRICE] : null, - 'website_id' => $this->getWebsiteId($rowData[self::COL_TIER_PRICE_WEBSITE]) + 'website_id' => $this->getWebSiteId($rowData[self::COL_TIER_PRICE_WEBSITE]) ]; } } @@ -482,9 +481,8 @@ protected function deleteProductTierPrices(array $listSku, $table) $this->addRowError(ValidatorInterface::ERROR_SKU_IS_EMPTY, 0); return false; } - } else { - return false; } + return false; } /** @@ -619,6 +617,7 @@ protected function processCountNewPrices(array $tierPrices) * Get product entity link field * * @return string + * @throws \Exception */ private function getProductEntityLinkField() { diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php index 25a9fc244fe51..d939a3f7c392e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php @@ -28,6 +28,7 @@ public function __construct($validators = []) * * @param array $value * @return bool + * @throws \Zend_Validate_Exception */ public function isValid($value) { diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdvancedPricingImportExport/LICENSE.txt b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdvancedPricingImportExport/LICENSE.txt rename to app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/LICENSE_AFL.txt b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/LICENSE_AFL.txt rename to app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/README.md b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/README.md new file mode 100644 index 0000000000000..7b4d0f3f0b12b --- /dev/null +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Advanced Pricing Import Export Functional Tests + +The Functional Test Module for **Magento Advanced Pricing Import Export** module. diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php index 48b4c58918740..57ceb7f5af275 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php @@ -151,10 +151,13 @@ protected function setUp() ] ); $this->exportConfig = $this->createMock(\Magento\ImportExport\Model\Export\Config::class); - $this->productFactory = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\ProductFactory::class, [ + $this->productFactory = $this->createPartialMock( + \Magento\Catalog\Model\ResourceModel\ProductFactory::class, + [ 'create', 'getTypeId', - ]); + ] + ); $this->attrSetColFactory = $this->createPartialMock( \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory::class, [ @@ -185,11 +188,14 @@ protected function setUp() \Magento\CatalogImportExport\Model\Import\Product\StoreResolver::class ); $this->groupRepository = $this->createMock(\Magento\Customer\Api\GroupRepositoryInterface::class); - $this->writer = $this->createPartialMock(\Magento\ImportExport\Model\Export\Adapter\AbstractAdapter::class, [ - 'setHeaderCols', - 'writeRow', - 'getContents', - ]); + $this->writer = $this->createPartialMock( + \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter::class, + [ + 'setHeaderCols', + 'writeRow', + 'getContents', + ] + ); $constructorMethods = [ 'initTypeModels', 'initAttributes', @@ -213,7 +219,7 @@ protected function setUp() '_getCustomerGroupById', 'correctExportData' ]); - $this->advancedPricing = $this->getMockbuilder( + $this->advancedPricing = $this->getMockBuilder( \Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing::class ) ->setMethods($mockMethods) @@ -347,6 +353,7 @@ protected function tearDown() * @param $object * @param $property * @return mixed + * @throws \ReflectionException */ protected function getPropertyValue($object, $property) { @@ -362,6 +369,8 @@ protected function getPropertyValue($object, $property) * @param $object * @param $property * @param $value + * @return mixed + * @throws \ReflectionException */ protected function setPropertyValue(&$object, $property, $value) { diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php index bb64acb558320..2c930237da831 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php @@ -181,6 +181,9 @@ public function testIsValidAddMessagesCall($value, $hasEmptyColumns, $customerGr $this->tierPrice->isValid($value); } + /** + * @return array + */ public function isValidResultFalseDataProvider() { return [ @@ -286,6 +289,9 @@ public function isValidResultFalseDataProvider() ]; } + /** + * @return array + */ public function isValidAddMessagesCallDataProvider() { return [ @@ -340,6 +346,7 @@ public function isValidAddMessagesCallDataProvider() * @param object $object * @param string $property * @return mixed + * @throws \ReflectionException */ protected function getPropertyValue($object, $property) { @@ -357,6 +364,7 @@ protected function getPropertyValue($object, $property) * @param string $property * @param mixed $value * @return object + * @throws \ReflectionException */ protected function setPropertyValue(&$object, $property, $value) { diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php index 5111b4932d7a8..b46e286e75007 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php @@ -27,7 +27,7 @@ class WebsiteTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->webSiteModel = $this->getMockBuilder(\Magento\Store\Model\WebSite::class) + $this->webSiteModel = $this->getMockBuilder(\Magento\Store\Model\Website::class) ->setMethods(['getBaseCurrency']) ->disableOriginalConstructor() ->getMock(); @@ -114,6 +114,9 @@ public function testGetAllWebsitesValue() $this->assertEquals($expectedResult, $result); } + /** + * @return array + */ public function isValidReturnDataProvider() { return [ diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/ValidatorTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/ValidatorTest.php index d9fce98826105..5ca534284a48d 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/ValidatorTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/ValidatorTest.php @@ -77,6 +77,9 @@ public function testInit() $this->validator->init(null); } + /** + * @return array + */ public function isValidDataProvider() { return [ diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php index 6d130d93ee6a5..340e81746f029 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php @@ -209,6 +209,10 @@ public function testGetEntityTypeCode() * Test method validateRow against its result. * * @dataProvider validateRowResultDataProvider + * @param array $rowData + * @param string|null $behavior + * @param bool $expectedResult + * @throws \ReflectionException */ public function testValidateRowResult($rowData, $behavior, $expectedResult) { @@ -234,6 +238,10 @@ public function testValidateRowResult($rowData, $behavior, $expectedResult) * Test method validateRow whether AddRowError is called. * * @dataProvider validateRowAddRowErrorCallDataProvider + * @param array $rowData + * @param string|null $behavior + * @param string $error + * @throws \ReflectionException */ public function testValidateRowAddRowErrorCall($rowData, $behavior, $error) { @@ -324,6 +332,13 @@ public function testSaveAdvancedPricing() * Take into consideration different data and check relative internal calls. * * @dataProvider saveAndReplaceAdvancedPricesAppendBehaviourDataProvider + * @param array $data + * @param string $tierCustomerGroupId + * @param string $groupCustomerGroupId + * @param string $tierWebsiteId + * @param string $groupWebsiteId + * @param array $expectedTierPrices + * @throws \ReflectionException */ public function testSaveAndReplaceAdvancedPricesAppendBehaviourDataAndCalls( $data, @@ -768,6 +783,9 @@ public function testSaveProductPrices($priceData, $oldSkus, $priceIn, $callNum) $this->invokeMethod($this->advancedPricing, 'saveProductPrices', [$priceData, 'table']); } + /** + * @return array + */ public function saveProductPricesDataProvider() { return [ @@ -839,6 +857,9 @@ public function testDeleteProductTierPrices( ); } + /** + * @return array + */ public function deleteProductTierPricesDataProvider() { return [ @@ -921,6 +942,9 @@ public function testProcessCountExistingPrices( $this->invokeMethod($this->advancedPricing, 'processCountExistingPrices', [$prices, 'table']); } + /** + * @return array + */ public function processCountExistingPricesDataProvider() { return [ @@ -947,6 +971,7 @@ public function processCountExistingPricesDataProvider() * @param $object * @param $property * @return mixed + * @throws \ReflectionException */ protected function getPropertyValue($object, $property) { @@ -963,6 +988,8 @@ protected function getPropertyValue($object, $property) * @param $object * @param $property * @param $value + * @return mixed + * @throws \ReflectionException */ protected function setPropertyValue(&$object, $property, $value) { @@ -980,8 +1007,8 @@ protected function setPropertyValue(&$object, $property, $value) * @param object $object * @param string $method * @param array $args - * - * @return mixed the method result. + * @return mixed + * @throws \ReflectionException */ private function invokeMethod($object, $method, $args = []) { @@ -998,6 +1025,7 @@ private function invokeMethod($object, $method, $args = []) * @param array $methods * * @return \PHPUnit_Framework_MockObject_MockObject + * @throws \ReflectionException */ private function getAdvancedPricingMock($methods = []) { diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index 1660104953504..12e1d9938f4bd 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -5,18 +5,17 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/framework": "100.3.*", - "magento/module-catalog": "101.2.*", - "magento/module-catalog-import-export": "100.3.*", - "magento/module-catalog-inventory": "100.3.*", - "magento/module-customer": "100.3.*", - "magento/module-eav": "100.3.*", - "magento/module-import-export": "100.3.*", - "magento/module-store": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-catalog": "*", + "magento/module-catalog-import-export": "*", + "magento/module-catalog-inventory": "*", + "magento/module-customer": "*", + "magento/module-eav": "*", + "magento/module-import-export": "*", + "magento/module-store": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/module.xml b/app/code/Magento/AdvancedPricingImportExport/etc/module.xml index ac4b8dafd0183..230fb17ae5544 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/module.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/module.xml @@ -6,6 +6,6 @@ */ --> - + diff --git a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php new file mode 100644 index 0000000000000..403a4d12cc17b --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Edit.php @@ -0,0 +1,31 @@ + + * @since 100.0.2 + */ +class Edit extends \Magento\Backend\Block\Widget\Grid\Container +{ + /** + * Enable grid container + * + * @return void + */ + protected function _construct() + { + $this->_blockGroup = 'Magento_AdvancedSearch'; + $this->_controller = 'adminhtml_search'; + $this->_headerText = __('Related Search Terms'); + $this->_addButtonLabel = __('Add New Search Term'); + parent::_construct(); + $this->buttonList->remove('add'); + } +} diff --git a/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php new file mode 100644 index 0000000000000..6bdfd3b0dd143 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Block/Adminhtml/Search/Grid.php @@ -0,0 +1,113 @@ + + * @since 100.0.2 + */ +class Grid extends \Magento\Backend\Block\Widget\Grid +{ + /** + * @var \Magento\AdvancedSearch\Model\Adminhtml\Search\Grid\Options + */ + protected $_options; + + /** + * @var \Magento\Framework\Registry + */ + protected $_registryManager; + + /** + * @var \Magento\Framework\Json\Helper\Data + */ + protected $jsonHelper; + + /** + * @param \Magento\Backend\Block\Template\Context $context + * @param \Magento\Backend\Helper\Data $backendHelper + * @param \Magento\AdvancedSearch\Model\Adminhtml\Search\Grid\Options $options + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\Json\Helper\Data $jsonHelper + * @param array $data + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + \Magento\Backend\Helper\Data $backendHelper, + \Magento\AdvancedSearch\Model\Adminhtml\Search\Grid\Options $options, + \Magento\Framework\Registry $registry, + \Magento\Framework\Json\Helper\Data $jsonHelper, + array $data = [] + ) { + $this->jsonHelper = $jsonHelper; + parent::__construct($context, $backendHelper, $data); + $this->_options = $options; + $this->_registryManager = $registry; + $this->setDefaultFilter(['query_id_selected' => 1]); + } + + /** + * Retrieve a value from registry by a key + * + * @return mixed + */ + public function getQuery() + { + return $this->_registryManager->registry('current_catalog_search'); + } + + /** + * Add column filter to collection + * + * @param \Magento\Backend\Block\Widget\Grid\Column $column + * @return $this + */ + protected function _addColumnFilterToCollection($column) + { + // Set custom filter for query selected flag + if ($column->getId() == 'query_id_selected' && $this->getQuery()->getId()) { + $selectedIds = $this->getSelectedQueries(); + if (empty($selectedIds)) { + $selectedIds = 0; + } + if ($column->getFilter()->getValue()) { + $this->getCollection()->addFieldToFilter('query_id', ['in' => $selectedIds]); + } elseif (!empty($selectedIds)) { + $this->getCollection()->addFieldToFilter('query_id', ['nin' => $selectedIds]); + } + } else { + parent::_addColumnFilterToCollection($column); + } + return $this; + } + + /** + * Retrieve selected related queries from grid + * + * @return array + */ + public function getSelectedQueries() + { + return $this->_options->toOptionArray(); + } + + /** + * Get queries json + * + * @return string + */ + public function getQueriesJson() + { + $queries = array_flip($this->getSelectedQueries()); + if (!empty($queries)) { + return $this->jsonHelper->jsonEncode($queries); + } + return '{}'; + } +} diff --git a/app/code/Magento/AdvancedSearch/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/AdvancedSearch/Block/Adminhtml/System/Config/TestConnection.php new file mode 100644 index 0000000000000..a546cfb126ba7 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Block/Adminhtml/System/Config/TestConnection.php @@ -0,0 +1,74 @@ +setTemplate('Magento_AdvancedSearch::system/config/testconnection.phtml'); + return $this; + } + + /** + * Unset some non-related element parameters + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + * @since 100.1.0 + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $element = clone $element; + $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + return parent::render($element); + } + + /** + * Get the button and scripts contents + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + * @since 100.1.0 + */ + protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $originalData = $element->getOriginalData(); + $this->addData( + [ + 'button_label' => __($originalData['button_label']), + 'html_id' => $element->getHtmlId(), + 'ajax_url' => $this->_urlBuilder->getUrl('catalog/search_system_config/testconnection'), + 'field_mapping' => str_replace('"', '\\"', json_encode($this->_getFieldMapping())) + ] + ); + + return $this->_toHtml(); + } + + /** + * Returns configuration fields required to perform the ping request + * + * @return array + * @since 100.1.0 + */ + protected function _getFieldMapping() + { + return ['engine' => 'catalog_search_engine']; + } +} diff --git a/app/code/Magento/AdvancedSearch/Block/Recommendations.php b/app/code/Magento/AdvancedSearch/Block/Recommendations.php new file mode 100644 index 0000000000000..1a23ea554bd91 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Block/Recommendations.php @@ -0,0 +1,14 @@ +searchDataProvider = $searchDataProvider; + $this->query = $queryFactory->get(); + $this->title = $title; + parent::__construct($context, $data); + } + + /** + * {@inheritdoc} + */ + public function getItems() + { + return $this->searchDataProvider->getItems($this->query); + } + + /** + * {@inheritdoc} + */ + public function isShowResultsCount() + { + return $this->searchDataProvider->isResultsCountEnabled(); + } + + /** + * {@inheritdoc} + */ + public function getLink($queryText) + { + return $this->getUrl('*/*/') . '?q=' . urlencode($queryText); + } + + /** + * {@inheritdoc} + */ + public function getTitle() + { + return __($this->title); + } +} diff --git a/app/code/Magento/AdvancedSearch/Block/SearchDataInterface.php b/app/code/Magento/AdvancedSearch/Block/SearchDataInterface.php new file mode 100644 index 0000000000000..299e68e558ad5 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Block/SearchDataInterface.php @@ -0,0 +1,36 @@ +clientResolver = $clientResolver; + $this->resultJsonFactory = $resultJsonFactory; + $this->tagFilter = $tagFilter; + } + + /** + * Check for connection to server + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $result = [ + 'success' => false, + 'errorMessage' => '', + ]; + $options = $this->getRequest()->getParams(); + + try { + if (empty($options['engine'])) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Missing search engine parameter.') + ); + } + $response = $this->clientResolver->create($options['engine'], $options)->testConnection(); + if ($response) { + $result['success'] = true; + } + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $result['errorMessage'] = $e->getMessage(); + } catch (\Exception $e) { + $message = __($e->getMessage()); + $result['errorMessage'] = $this->tagFilter->filter($message); + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData($result); + } +} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdminNotification/LICENSE.txt b/app/code/Magento/AdvancedSearch/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdminNotification/LICENSE.txt rename to app/code/Magento/AdvancedSearch/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdminNotification/LICENSE_AFL.txt b/app/code/Magento/AdvancedSearch/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/AdminNotification/LICENSE_AFL.txt rename to app/code/Magento/AdvancedSearch/LICENSE_AFL.txt diff --git a/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProvider.php b/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProvider.php new file mode 100644 index 0000000000000..ef1f9890e02d1 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProvider.php @@ -0,0 +1,39 @@ + [field name1 => value1, ...], ...] + */ +class AdditionalFieldsProvider implements AdditionalFieldsProviderInterface +{ + /** + * @var AdditionalFieldsProviderInterface[] + */ + private $fieldsProviders; + + /** + * @param AdditionalFieldsProviderInterface[] $fieldsProviders + */ + public function __construct(array $fieldsProviders) + { + $this->fieldsProviders = $fieldsProviders; + } + + /** + * {@inheritdoc} + */ + public function getFields(array $productIds, $storeId) + { + $fields = []; + foreach ($this->fieldsProviders as $fieldsProvider) { + $fields[] = $fieldsProvider->getFields($productIds, $storeId); + } + + return array_replace_recursive(...$fields); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProviderInterface.php b/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProviderInterface.php new file mode 100644 index 0000000000000..d7151236c6170 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Adapter/DataMapper/AdditionalFieldsProviderInterface.php @@ -0,0 +1,25 @@ + [field name1 => value1, ...], ...] + * @api + * @since 100.2.0 + */ +interface AdditionalFieldsProviderInterface +{ + /** + * Get additional fields for data mapper during search indexer based on product ids and store id. + * + * @param array $productIds + * @param int $storeId + * @return array + * @since 100.2.0 + */ + public function getFields(array $productIds, $storeId); +} diff --git a/app/code/Magento/AdvancedSearch/Model/Adminhtml/Search/Grid/Options.php b/app/code/Magento/AdvancedSearch/Model/Adminhtml/Search/Grid/Options.php new file mode 100644 index 0000000000000..b139689dbc234 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Adminhtml/Search/Grid/Options.php @@ -0,0 +1,60 @@ +_request = $request; + $this->_registryManager = $registry; + $this->_searchResourceModel = $searchResourceModel; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + $queries = $this->_request->getPost('selected_queries'); + + $currentQueryId = $this->_registryManager->registry('current_catalog_search')->getId(); + $queryIds = []; + if ($queries === null && !empty($currentQueryId)) { + $queryIds = $this->_searchResourceModel->getRelatedQueries($currentQueryId); + } + return $queryIds; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php new file mode 100644 index 0000000000000..05eb513d68399 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php @@ -0,0 +1,47 @@ +objectManager = $objectManager; + $this->clientClass = $clientClass; + } + + /** + * Return search client + * + * @param array $options + * @return ClientInterface + */ + public function create(array $options = []) + { + return $this->objectManager->create( + $this->clientClass, + ['options' => $options] + ); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Client/ClientFactoryInterface.php b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactoryInterface.php new file mode 100644 index 0000000000000..acacbb1c093fa --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactoryInterface.php @@ -0,0 +1,22 @@ +objectManager = $objectManager; + $this->clientFactoryPool = $clientFactories; + $this->clientOptionsPool = $clientOptions; + $this->engineResolver = $engineResolver; + } + + /** + * Returns configured search engine + * + * @return string + * @since 100.1.0 + */ + public function getCurrentEngine() + { + return $this->engineResolver->getCurrentSearchEngine(); + } + + /** + * Create client instance + * + * @param string $engine + * @param array $data + * @return ClientInterface + * @since 100.1.0 + */ + public function create($engine = '', array $data = []) + { + $engine = $engine ?: $this->getCurrentEngine(); + + if (!isset($this->clientFactoryPool[$engine])) { + throw new \LogicException( + 'There is no such client factory: ' . $engine + ); + } + $factoryClass = $this->clientFactoryPool[$engine]; + $factory = $this->objectManager->create($factoryClass); + if (!($factory instanceof ClientFactoryInterface)) { + throw new \InvalidArgumentException( + 'Client factory must implement \Magento\AdvancedSearch\Model\Client\ClientFactoryInterface' + ); + } + + $optionsClass = $this->clientOptionsPool[$engine]; + $clientOptions = $this->objectManager->create($optionsClass); + if (!($clientOptions instanceof ClientOptionsInterface)) { + throw new \InvalidArgumentException( + 'Client options must implement \Magento\AdvancedSearch\Model\Client\ClientInterface' + ); + } + + $client = $factory->create($clientOptions->prepareClientOptions($data)); + + return $client; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/DataProvider/Suggestions.php b/app/code/Magento/AdvancedSearch/Model/DataProvider/Suggestions.php new file mode 100644 index 0000000000000..c76811c854514 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/DataProvider/Suggestions.php @@ -0,0 +1,28 @@ +clientOptions = $clientOptions; + $this->engineResolver = $engineResolver; + } + + /** + * Invalidate indexer on customer group save + * + * @param Group $subject + * @param \Closure $proceed + * @param AbstractModel $group + * @return Attribute + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundSave( + Group $subject, + \Closure $proceed, + AbstractModel $group + ) { + $needInvalidation = + ($this->engineResolver->getCurrentSearchEngine() != EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE) + && ($group->isObjectNew() || $group->dataHasChangedFor('tax_class_id')); + $result = $proceed($group); + if ($needInvalidation) { + $this->indexerRegistry->get(Fulltext::INDEXER_ID)->invalidate(); + } + return $result; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php new file mode 100644 index 0000000000000..c0c224766eb3c --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php @@ -0,0 +1,162 @@ +scopeConfig = $scopeConfig; + $this->searchLayer = $layerResolver->get(); + $this->recommendationsFactory = $recommendationsFactory; + $this->queryResultFactory = $queryResultFactory; + } + + /** + * Is Results Count Enabled + * + * @return bool + */ + public function isResultsCountEnabled() + { + return $this->scopeConfig->isSetFlag( + self::CONFIG_RESULTS_COUNT_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * @inheritdoc + */ + public function getItems(QueryInterface $query) + { + $recommendations = []; + + if (!$this->isSearchRecommendationsEnabled()) { + return []; + } + + foreach ($this->getSearchRecommendations($query) as $recommendation) { + $recommendations[] = $this->queryResultFactory->create( + [ + 'queryText' => $recommendation['query_text'], + 'resultsCount' => $recommendation['num_results'], + ] + ); + } + return $recommendations; + } + + /** + * Return Search Recommendations + * + * @param QueryInterface $query + * @return array + */ + private function getSearchRecommendations(\Magento\Search\Model\QueryInterface $query) + { + $recommendations = []; + + if ($this->isSearchRecommendationsEnabled()) { + $productCollection = $this->searchLayer->getProductCollection(); + $params = ['store_id' => $productCollection->getStoreId()]; + + /** @var \Magento\AdvancedSearch\Model\ResourceModel\Recommendations $recommendationsResource */ + $recommendationsResource = $this->recommendationsFactory->create(); + $recommendations = $recommendationsResource->getRecommendationsByQuery( + $query->getQueryText(), + $params, + $this->getSearchRecommendationsCount() + ); + } + + return $recommendations; + } + + /** + * Is Search Recommendations Enabled + * + * @return bool + */ + private function isSearchRecommendationsEnabled() + { + return $this->scopeConfig->isSetFlag( + self::CONFIG_IS_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Return Search Recommendations Count + * + * @return int + */ + private function getSearchRecommendationsCount() + { + return (int)$this->scopeConfig->getValue( + self::CONFIG_RESULTS_COUNT, + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Recommendations/SaveSearchQueryRelationsObserver.php b/app/code/Magento/AdvancedSearch/Model/Recommendations/SaveSearchQueryRelationsObserver.php new file mode 100644 index 0000000000000..5f5d4122d97a5 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/Recommendations/SaveSearchQueryRelationsObserver.php @@ -0,0 +1,48 @@ +recommendationsFactory = $recommendationsFactory; + } + + /** + * Save search query relations after save search query + * + * @param EventObserver $observer + * @return void + */ + public function execute(EventObserver $observer) + { + $searchQueryModel = $observer->getEvent()->getDataObject(); + $queryId = $searchQueryModel->getId(); + $relatedQueries = $searchQueryModel->getSelectedQueriesGrid(); + + if (strlen($relatedQueries) == 0) { + $relatedQueries = []; + } else { + $relatedQueries = explode('&', $relatedQueries); + } + + $this->recommendationsFactory->create()->saveRelatedQueries($queryId, $relatedQueries); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php new file mode 100644 index 0000000000000..b20872da2f8e7 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php @@ -0,0 +1,220 @@ +storeManager = $storeManager; + $this->metadataPool = $metadataPool; + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(IndexScopeResolverInterface::class); + $this->dimensionCollectionFactory = $dimensionCollectionFactory + ?: ObjectManager::getInstance()->get(DimensionCollectionFactory::class); + } + + /** + * Implementation of abstract construct + * @return void + * @since 100.1.0 + */ + protected function _construct() + { + } + + /** + * Return array of price data per customer and website by products + * + * @param null|array $productIds + * @return array + * @since 100.1.0 + */ + protected function _getCatalogProductPriceData($productIds = null) + { + $connection = $this->getConnection(); + $catalogProductIndexPriceSelect = []; + + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + if (!isset($dimensions[WebsiteDimensionProvider::DIMENSION_NAME]) || + $this->websiteId === null || + $dimensions[WebsiteDimensionProvider::DIMENSION_NAME]->getValue() === $this->websiteId) { + $select = $connection->select()->from( + $this->tableResolver->resolve('catalog_product_index_price', $dimensions), + ['entity_id', 'customer_group_id', 'website_id', 'min_price'] + ); + if ($productIds) { + $select->where('entity_id IN (?)', $productIds); + } + $catalogProductIndexPriceSelect[] = $select; + } + } + + $catalogProductIndexPriceUnionSelect = $connection->select()->union($catalogProductIndexPriceSelect); + + $result = []; + foreach ($connection->fetchAll($catalogProductIndexPriceUnionSelect) as $row) { + $result[$row['website_id']][$row['entity_id']][$row['customer_group_id']] = round($row['min_price'], 2); + } + + return $result; + } + + /** + * Retrieve price data for product + * + * @param null|array $productIds + * @param int $storeId + * @return array + * @since 100.1.0 + */ + public function getPriceIndexData($productIds, $storeId) + { + $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); + + $this->websiteId = $websiteId; + $priceProductsIndexData = $this->_getCatalogProductPriceData($productIds); + $this->websiteId = null; + + if (!isset($priceProductsIndexData[$websiteId])) { + return []; + } + + return $priceProductsIndexData[$websiteId]; + } + + /** + * Prepare system index data for products. + * + * @param int $storeId + * @param null|array $productIds + * @return array + * @since 100.1.0 + */ + public function getCategoryProductIndexData($storeId = null, $productIds = null) + { + $connection = $this->getConnection(); + + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $storeId); + + $catalogCategoryProductTableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + + $select = $connection->select()->from( + [$catalogCategoryProductTableName], + ['category_id', 'product_id', 'position', 'store_id'] + )->where( + 'store_id = ?', + $storeId + ); + + if ($productIds) { + $select->where('product_id IN (?)', $productIds); + } + + $result = []; + foreach ($connection->fetchAll($select) as $row) { + $result[$row['product_id']][$row['category_id']] = $row['position']; + } + + return $result; + } + + /** + * Retrieve moved categories product ids + * + * @param int $categoryId + * @return array + * @since 100.1.0 + */ + public function getMovedCategoryProductIds($categoryId) + { + $connection = $this->getConnection(); + + $identifierField = $this->metadataPool->getMetadata(CategoryInterface::class)->getIdentifierField(); + + $select = $connection->select()->distinct()->from( + ['c_p' => $this->getTable('catalog_category_product')], + ['product_id'] + )->join( + ['c_e' => $this->getTable('catalog_category_entity')], + 'c_p.category_id = c_e.' . $identifierField, + [] + )->where( + $connection->quoteInto('c_e.path LIKE ?', '%/' . $categoryId . '/%') + )->orWhere( + 'c_p.category_id = ?', + $categoryId + ); + + return $connection->fetchCol($select); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php new file mode 100644 index 0000000000000..c19c1d67d81f7 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Recommendations.php @@ -0,0 +1,227 @@ + + * @api + * @since 100.0.2 + */ +class Recommendations extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + + /** + * Search query model + * + * @var \Magento\Search\Model\Query + */ + protected $_searchQueryModel; + + /** + * Construct + * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param \Magento\Search\Model\QueryFactory $queryFactory + * @param string $connectionName + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Search\Model\QueryFactory $queryFactory, + $connectionName = null + ) { + parent::__construct($context, $connectionName); + $this->_searchQueryModel = $queryFactory->create(); + } + + /** + * Init main table + * + * @return void + */ + protected function _construct() + { + $this->_init('catalogsearch_recommendations', 'id'); + } + + /** + * Save search relations + * + * @param int $queryId + * @param array $relatedQueries + * @return $this + */ + public function saveRelatedQueries($queryId, $relatedQueries = []) + { + $connection = $this->getConnection(); + $whereOr = []; + if (count($relatedQueries) > 0) { + $whereOr[] = implode( + ' AND ', + [ + $connection->quoteInto('query_id=?', $queryId), + $connection->quoteInto('relation_id NOT IN(?)', $relatedQueries) + ] + ); + $whereOr[] = implode( + ' AND ', + [ + $connection->quoteInto('relation_id = ?', $queryId), + $connection->quoteInto('query_id NOT IN(?)', $relatedQueries) + ] + ); + } else { + $whereOr[] = $connection->quoteInto('query_id = ?', $queryId); + $whereOr[] = $connection->quoteInto('relation_id = ?', $queryId); + } + $whereCond = '(' . implode(') OR (', $whereOr) . ')'; + $connection->delete($this->getMainTable(), $whereCond); + + $existsRelatedQueries = $this->getRelatedQueries($queryId); + $neededRelatedQueries = array_diff($relatedQueries, $existsRelatedQueries); + foreach ($neededRelatedQueries as $relationId) { + $connection->insert($this->getMainTable(), ["query_id" => $queryId, "relation_id" => $relationId]); + } + return $this; + } + + /** + * Retrieve related search queries + * + * @param int|array $queryId + * @param bool $limit + * @param bool $order + * @return array + */ + public function getRelatedQueries($queryId, $limit = false, $order = false) + { + $collection = $this->_searchQueryModel->getResourceCollection(); + $connection = $this->getConnection(); + + $queryIdCond = $connection->quoteInto('main_table.query_id IN (?)', $queryId); + + $collection->getSelect()->join( + ['sr' => $collection->getTable('catalogsearch_recommendations')], + '(sr.query_id=main_table.query_id OR sr.relation_id=main_table.query_id) AND ' . $queryIdCond + )->reset( + \Magento\Framework\DB\Select::COLUMNS + )->columns( + [ + 'rel_id' => $connection->getCheckSql( + 'main_table.query_id=sr.query_id', + 'sr.relation_id', + 'sr.query_id' + ), + ] + ); + if (!empty($limit)) { + $collection->getSelect()->limit($limit); + } + if (!empty($order)) { + $collection->getSelect()->order($order); + } + + $queryIds = $connection->fetchCol($collection->getSelect()); + return $queryIds; + } + + /** + * Retrieve related search queries by single query + * + * @param string $query + * @param array $params + * @param int $searchRecommendationsCount + * @return array + */ + public function getRecommendationsByQuery($query, $params, $searchRecommendationsCount) + { + $this->_searchQueryModel->loadByQueryText($query); + + if (isset($params['store_id'])) { + $this->_searchQueryModel->setStoreId($params['store_id']); + } + $relatedQueriesIds = $this->loadByQuery($query, $searchRecommendationsCount); + $relatedQueries = []; + if (count($relatedQueriesIds)) { + $connection = $this->getConnection(); + $mainTable = $this->_searchQueryModel->getResourceCollection()->getMainTable(); + $select = $connection->select()->from( + ['main_table' => $mainTable], + ['query_text', 'num_results'] + )->where( + 'query_id IN(?)', + $relatedQueriesIds + )->where( + 'num_results > 0' + ); + $relatedQueries = $connection->fetchAll($select); + } + + return $relatedQueries; + } + + /** + * Retrieve search terms which are started with $queryWords + * + * @param string $query + * @param int $searchRecommendationsCount + * @return array + */ + protected function loadByQuery($query, $searchRecommendationsCount) + { + $connection = $this->getConnection(); + $queryId = $this->_searchQueryModel->getId(); + $relatedQueries = $this->getRelatedQueries($queryId, $searchRecommendationsCount, 'num_results DESC'); + if ($searchRecommendationsCount - count($relatedQueries) < 1) { + return $relatedQueries; + } + + $queryWords = [$query]; + if (strpos($query, ' ') !== false) { + $queryWords = array_unique(array_merge($queryWords, explode(' ', $query))); + foreach ($queryWords as $key => $word) { + $queryWords[$key] = trim($word); + if (strlen($word) < 3) { + unset($queryWords[$key]); + } + } + } + + $likeCondition = []; + foreach ($queryWords as $word) { + $likeCondition[] = $connection->quoteInto('query_text LIKE ?', $word . '%'); + } + $likeCondition = implode(' OR ', $likeCondition); + + $select = $connection->select()->from( + $this->_searchQueryModel->getResource()->getMainTable(), + ['query_id'] + )->where( + new \Zend_Db_Expr($likeCondition) + )->where( + 'store_id=?', + $this->_searchQueryModel->getStoreId() + )->order( + 'num_results DESC' + )->limit( + $searchRecommendationsCount + 1 + ); + $ids = $connection->fetchCol($select); + + if (!is_array($ids)) { + $ids = []; + } + + $key = array_search($queryId, $ids); + if ($key !== false) { + unset($ids[$key]); + } + $ids = array_unique(array_merge($relatedQueries, $ids)); + $ids = array_slice($ids, 0, $searchRecommendationsCount); + return $ids; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Search/Grid/Collection.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Search/Grid/Collection.php new file mode 100644 index 0000000000000..59263f308117c --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Search/Grid/Collection.php @@ -0,0 +1,80 @@ +_registryManager = $registry; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $storeManager, + $resourceHelper, + $connection, + $resource + ); + } + + /** + * Initialize select + * + * @return $this + */ + protected function _initSelect() + { + parent::_initSelect(); + $queryId = $this->getQuery()->getId(); + if ($queryId) { + $this->addFieldToFilter('query_id', ['nin' => $queryId]); + } + return $this; + } + + /** + * Retrieve a value from registry by a key + * + * @return \Magento\Search\Model\Query + */ + public function getQuery() + { + return $this->_registryManager->registry('current_catalog_search'); + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/SuggestedQueries.php b/app/code/Magento/AdvancedSearch/Model/SuggestedQueries.php new file mode 100644 index 0000000000000..60f76682fc164 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/SuggestedQueries.php @@ -0,0 +1,88 @@ +engineResolver = $engineResolver; + $this->objectManager = $objectManager; + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function isResultsCountEnabled() + { + return $this->getDataProvider()->isResultsCountEnabled(); + } + + /** + * {@inheritdoc} + */ + public function getItems(QueryInterface $query) + { + return $this->getDataProvider()->getItems($query); + } + + /** + * Returns DataProvider for SuggestedQueries + * + * @return SuggestedQueriesInterface|SuggestedQueriesInterface[] + * @throws \Exception + */ + private function getDataProvider() + { + if (empty($this->dataProvider)) { + $currentEngine = $this->engineResolver->getCurrentSearchEngine(); + $this->dataProvider = $this->objectManager->create($this->data[$currentEngine]); + if (!$this->dataProvider instanceof SuggestedQueriesInterface) { + throw new \InvalidArgumentException( + 'Data provider must implement \Magento\AdvancedSearch\Model\SuggestedQueriesInterface' + ); + } + } + return $this->dataProvider; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/SuggestedQueriesInterface.php b/app/code/Magento/AdvancedSearch/Model/SuggestedQueriesInterface.php new file mode 100644 index 0000000000000..64ab45ceb145e --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Model/SuggestedQueriesInterface.php @@ -0,0 +1,42 @@ +dataProvider = $this->getMockBuilder(\Magento\AdvancedSearch\Model\SuggestedQueriesInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getItems', 'isResultsCountEnabled']) + ->getMockForAbstractClass(); + + $this->searchQuery = $this->getMockBuilder(\Magento\Search\Model\QueryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getQueryText']) + ->getMockForAbstractClass(); + $this->queryFactory = $this->getMockBuilder(\Magento\Search\Model\QueryFactoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMockForAbstractClass(); + $this->queryFactory->expects($this->once()) + ->method('get') + ->will($this->returnValue($this->searchQuery)); + $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\Template\Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->block = $this->getMockBuilder(\Magento\AdvancedSearch\Block\SearchData::class)->setConstructorArgs( + [ + $this->context, + $this->dataProvider, + $this->queryFactory, + 'Test Title', + [], + ] + ) + ->setMethods(['getUrl']) + ->getMockForAbstractClass(); + } + + public function testGetSuggestions() + { + $value = [1, 2, 3, 100500]; + + $this->dataProvider->expects($this->once()) + ->method('getItems') + ->with($this->searchQuery) + ->will($this->returnValue($value)); + $actualValue = $this->block->getItems(); + $this->assertEquals($value, $actualValue); + } + + public function testGetLink() + { + $searchQuery = 'Some test search query'; + $expectedResult = '?q=Some+test+search+query'; + $actualResult = $this->block->getLink($searchQuery); + $this->assertEquals($expectedResult, $actualResult); + } + + public function testIsShowResultsCount() + { + $value = 'qwertyasdfzxcv'; + $this->dataProvider->expects($this->once()) + ->method('isResultsCountEnabled') + ->will($this->returnValue($value)); + $this->assertEquals($value, $this->block->isShowResultsCount()); + } +} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Controller/Adminhtml/Search/System/Config/TestConnectionTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Controller/Adminhtml/Search/System/Config/TestConnectionTest.php new file mode 100644 index 0000000000000..6215d79fc41ee --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Controller/Adminhtml/Search/System/Config/TestConnectionTest.php @@ -0,0 +1,169 @@ +requestMock = $this->createPartialMock(\Magento\Framework\App\Request\Http::class, ['getParams']); + $responseMock = $this->createMock(\Magento\Framework\App\Response\Http::class); + + $context = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class) + ->setMethods(['getRequest', 'getResponse', 'getMessageManager', 'getSession']) + ->setConstructorArgs( + $helper->getConstructArguments( + \Magento\Backend\App\Action\Context::class, + [ + 'request' => $this->requestMock + ] + ) + ) + ->getMock(); + $context->expects($this->once())->method('getRequest')->will($this->returnValue($this->requestMock)); + $context->expects($this->once())->method('getResponse')->will($this->returnValue($responseMock)); + + $this->clientResolverMock = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientResolver::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->clientMock = $this->createMock(\Magento\AdvancedSearch\Model\Client\ClientInterface::class); + + $this->resultJson = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultJsonFactory = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->tagFilterMock = $this->getMockBuilder(\Magento\Framework\Filter\StripTags::class) + ->disableOriginalConstructor() + ->setMethods(['filter']) + ->getMock(); + + $this->controller = new TestConnection( + $context, + $this->clientResolverMock, + $this->resultJsonFactory, + $this->tagFilterMock + ); + } + + public function testExecuteEmptyEngine() + { + $this->requestMock->expects($this->once())->method('getParams') + ->will($this->returnValue(['engine' => ''])); + + $this->resultJsonFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->resultJson)); + + $result = ['success' => false, 'errorMessage' => 'Missing search engine parameter.']; + + $this->resultJson->expects($this->once())->method('setData') + ->with($this->equalTo($result)); + + $this->controller->execute(); + } + + public function testExecute() + { + $this->requestMock->expects($this->once())->method('getParams') + ->will($this->returnValue(['engine' => 'engineName'])); + + $this->clientResolverMock->expects($this->once())->method('create') + ->with($this->equalTo('engineName')) + ->will($this->returnValue($this->clientMock)); + + $this->clientMock->expects($this->once())->method('testConnection') + ->will($this->returnValue(true)); + + $this->resultJsonFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->resultJson)); + + $result = ['success' => true, 'errorMessage' => '']; + + $this->resultJson->expects($this->once())->method('setData') + ->with($this->equalTo($result)); + + $this->controller->execute(); + } + + public function testExecutePingFailed() + { + $this->requestMock->expects($this->once())->method('getParams') + ->will($this->returnValue(['engine' => 'engineName'])); + + $this->clientResolverMock->expects($this->once())->method('create') + ->with($this->equalTo('engineName')) + ->will($this->returnValue($this->clientMock)); + + $this->clientMock->expects($this->once())->method('testConnection') + ->will($this->returnValue(false)); + + $this->resultJsonFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->resultJson)); + + $result = ['success' => false, 'errorMessage' => '']; + + $this->resultJson->expects($this->once())->method('setData') + ->with($this->equalTo($result)); + + $this->controller->execute(); + } +} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Model/Client/ClientResolverTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Model/Client/ClientResolverTest.php new file mode 100644 index 0000000000000..0cad0a2e8301c --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Model/Client/ClientResolverTest.php @@ -0,0 +1,108 @@ +engineResolverMock = $this->getMockBuilder(EngineResolverInterface::class) + ->getMockForAbstractClass(); + + $this->objectManager = $this->createMock(ObjectManagerInterface::class); + + $this->model = new ClientResolver( + $this->objectManager, + ['engineName' => 'engineFactoryClass'], + ['engineName' => 'engineOptionClass'], + $this->engineResolverMock + ); + } + + public function testCreate() + { + $this->engineResolverMock->expects($this->once())->method('getCurrentSearchEngine') + ->will($this->returnValue('engineName')); + + $factoryMock = $this->createMock(ClientFactoryInterface::class); + + $clientMock = $this->createMock(ClientInterface::class); + + $clientOptionsMock = $this->createMock(ClientOptionsInterface::class); + + $this->objectManager->expects($this->exactly(2))->method('create') + ->withConsecutive( + [$this->equalTo('engineFactoryClass')], + [$this->equalTo('engineOptionClass')] + ) + ->willReturnOnConsecutiveCalls( + $factoryMock, + $clientOptionsMock + ); + + $clientOptionsMock->expects($this->once())->method('prepareClientOptions') + ->with([]) + ->will($this->returnValue(['parameters'])); + + $factoryMock->expects($this->once())->method('create') + ->with($this->equalTo(['parameters'])) + ->will($this->returnValue($clientMock)); + + $result = $this->model->create(); + $this->assertInstanceOf(ClientInterface::class, $result); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCreateExceptionThrown() + { + $this->objectManager->expects($this->once())->method('create') + ->with($this->equalTo('engineFactoryClass')) + ->will($this->returnValue('t')); + + $this->model->create('engineName'); + } + + /** + * @expectedException LogicException + */ + public function testCreateLogicException() + { + $this->model->create('input'); + } + + public function testGetCurrentEngine() + { + $this->engineResolverMock->expects($this->once())->method('getCurrentSearchEngine') + ->will($this->returnValue('engineName')); + + $this->assertEquals('engineName', $this->model->getCurrentEngine()); + } +} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/CustomerGroupTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/CustomerGroupTest.php new file mode 100644 index 0000000000000..e6de135aab473 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/CustomerGroupTest.php @@ -0,0 +1,131 @@ +subjectMock = $this->createMock(\Magento\Customer\Model\ResourceModel\Group::class); + $this->customerOptionsMock = $this->createMock( + \Magento\AdvancedSearch\Model\Client\ClientOptionsInterface::class + ); + $this->indexerMock = $this->getMockForAbstractClass( + \Magento\Framework\Indexer\IndexerInterface::class, + [], + '', + false, + false, + true, + ['getId', 'getState', '__wakeup'] + ); + $this->indexerRegistryMock = $this->createPartialMock( + \Magento\Framework\Indexer\IndexerRegistry::class, + ['get'] + ); + $this->engineResolverMock = $this->createPartialMock( + \Magento\Search\Model\EngineResolver::class, + ['getCurrentSearchEngine'] + ); + $this->model = new CustomerGroup( + $this->indexerRegistryMock, + $this->customerOptionsMock, + $this->engineResolverMock + ); + } + + /** + * @param string $searchEngine + * @param bool $isObjectNew + * @param bool $isTaxClassIdChanged + * @param int $invalidateCounter + * @return void + * @dataProvider aroundSaveDataProvider + */ + public function testAroundSave($searchEngine, $isObjectNew, $isTaxClassIdChanged, $invalidateCounter) + { + $this->engineResolverMock->expects($this->once()) + ->method('getCurrentSearchEngine') + ->will($this->returnValue($searchEngine)); + + $groupMock = $this->createPartialMock( + \Magento\Customer\Model\Group::class, + ['dataHasChangedFor', 'isObjectNew', '__wakeup'] + ); + $groupMock->expects($this->any())->method('isObjectNew')->will($this->returnValue($isObjectNew)); + $groupMock->expects($this->any()) + ->method('dataHasChangedFor') + ->with('tax_class_id') + ->will($this->returnValue($isTaxClassIdChanged)); + + $closureMock = function (\Magento\Customer\Model\Group $object) use ($groupMock) { + $this->assertEquals($object, $groupMock); + return $this->subjectMock; + }; + + $this->indexerMock->expects($this->exactly($invalidateCounter))->method('invalidate'); + $this->indexerRegistryMock->expects($this->exactly($invalidateCounter)) + ->method('get') + ->with(\Magento\CatalogSearch\Model\Indexer\Fulltext::INDEXER_ID) + ->will($this->returnValue($this->indexerMock)); + + $this->assertEquals( + $this->subjectMock, + $this->model->aroundSave($this->subjectMock, $closureMock, $groupMock) + ); + } + + /** + * @return array + */ + public function aroundSaveDataProvider() + { + return [ + ['mysql', false, false, 0], + ['mysql', false, true, 0], + ['mysql', true, false, 0], + ['mysql', true, true, 0], + ['custom', false, false, 0], + ['custom', false, true, 1], + ['custom', true, false, 1], + ['custom', true, true, 1], + ]; + } +} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php new file mode 100644 index 0000000000000..1f37e40842f54 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -0,0 +1,103 @@ +storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->resourceContextMock = $this->createMock(Context::class); + $this->resourceConnectionMock = $this->createMock(ResourceConnection::class); + $this->resourceContextMock->expects($this->any()) + ->method('getResources') + ->willReturn($this->resourceConnectionMock); + $this->adapterMock = $this->createMock(AdapterInterface::class); + $this->resourceConnectionMock->expects($this->any())->method('getConnection')->willReturn($this->adapterMock); + $this->metadataPoolMock = $this->createMock(MetadataPool::class); + + $indexScopeResolverMock = $this->createMock( + \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver::class + ); + $traversableMock = $this->createMock(\Traversable::class); + $dimensionsMock = $this->createMock(\Magento\Framework\Indexer\MultiDimensionProvider::class); + $dimensionsMock->method('getIterator')->willReturn($traversableMock); + $dimensionFactoryMock = $this->createMock( + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + ); + $dimensionFactoryMock->method('create')->willReturn($dimensionsMock); + + $this->model = new Index( + $this->resourceContextMock, + $this->storeManagerMock, + $this->metadataPoolMock, + 'connectionName', + $indexScopeResolverMock, + $dimensionFactoryMock + ); + } + + public function testGetPriceIndexDataUsesFrontendPriceIndexerTable() + { + $storeId = 1; + $storeMock = $this->createMock(StoreInterface::class); + $storeMock->expects($this->any())->method('getId')->willReturn($storeId); + $storeMock->method('getWebsiteId')->willReturn(1); + $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); + + $selectMock = $this->createMock(Select::class); + $selectMock->expects($this->any())->method('from')->willReturnSelf(); + $selectMock->expects($this->any())->method('where')->willReturnSelf(); + $selectMock->expects($this->any())->method('union')->willReturnSelf(); + $this->adapterMock->expects($this->once())->method('select')->willReturn($selectMock); + $this->adapterMock->expects($this->once())->method('fetchAll')->with($selectMock)->willReturn([]); + + $this->assertEmpty($this->model->getPriceIndexData([1], $storeId)); + } +} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Model/SuggestedQueriesTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Model/SuggestedQueriesTest.php new file mode 100644 index 0000000000000..d349ed3e3ce93 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Model/SuggestedQueriesTest.php @@ -0,0 +1,131 @@ +engineResolverMock = $this->getMockBuilder(\Magento\Search\Model\EngineResolver::class) + ->setMethods(['getCurrentSearchEngine']) + ->disableOriginalConstructor() + ->getMock(); + $this->engineResolverMock->expects($this->any()) + ->method('getCurrentSearchEngine') + ->willReturn('my_engine'); + + /** + * @var \Magento\AdvancedSearch\Model\SuggestedQueriesInterface| + * \PHPUnit_Framework_MockObject_MockObject + */ + $suggestedQueriesMock = $this->createMock(\Magento\AdvancedSearch\Model\SuggestedQueriesInterface::class); + $suggestedQueriesMock->expects($this->any()) + ->method('isResultsCountEnabled') + ->willReturn(true); + $suggestedQueriesMock->expects($this->any()) + ->method('getItems') + ->willReturn([]); + + $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerMock->expects($this->any()) + ->method('create') + ->with('search_engine') + ->willReturn($suggestedQueriesMock); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $this->objectManagerHelper->getObject( + \Magento\AdvancedSearch\Model\SuggestedQueries::class, + [ + 'engineResolver' => $this->engineResolverMock, + 'objectManager' => $this->objectManagerMock, + 'data' => ['my_engine' => 'search_engine'] + ] + ); + } + + /** + * Test isResultsCountEnabled method. + * + * @return void + */ + public function testIsResultsCountEnabled() + { + $result = $this->model->isResultsCountEnabled(); + $this->assertTrue($result); + } + + /** + * Test isResultsCountEnabled() method failure. + * @expectedException \InvalidArgumentException + * + * @return void + */ + public function testIsResultsCountEnabledException() + { + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock->expects($this->once()) + ->method('create') + ->willReturn(null); + + $objectManagerHelper = new ObjectManagerHelper($this); + /* @var $model \Magento\AdvancedSearch\Model\SuggestedQueries */ + $model = $objectManagerHelper->getObject( + \Magento\AdvancedSearch\Model\SuggestedQueries::class, + [ + 'engineResolver' => $this->engineResolverMock, + 'objectManager' => $objectManagerMock, + 'data' => ['my_engine' => 'search_engine'] + ] + ); + $model->isResultsCountEnabled(); + } + + /** + * Test testGetItems() method. + * + * @return void + */ + public function testGetItems() + { + /** @var $queryInterfaceMock \Magento\Search\Model\QueryInterface */ + $queryInterfaceMock = $this->createMock(\Magento\Search\Model\QueryInterface::class); + $result = $this->model->getItems($queryInterfaceMock); + $this->assertEquals([], $result); + } +} diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json new file mode 100644 index 0000000000000..c6a5af07e60a7 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -0,0 +1,31 @@ +{ + "name": "magento/module-advanced-search", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-catalog": "*", + "magento/module-catalog-search": "*", + "magento/module-config": "*", + "magento/module-customer": "*", + "magento/module-search": "*", + "magento/module-store": "*", + "php": "~7.1.3||~7.2.0" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AdvancedSearch\\": "" + } + } +} diff --git a/app/code/Magento/AdvancedSearch/etc/adminhtml/events.xml b/app/code/Magento/AdvancedSearch/etc/adminhtml/events.xml new file mode 100644 index 0000000000000..b4d0f63a2bab4 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/adminhtml/events.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/etc/adminhtml/routes.xml b/app/code/Magento/AdvancedSearch/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..286d1537d40cc --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml b/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..fa7774f5cec1d --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml @@ -0,0 +1,74 @@ + + + + +
+ + + + When you enable this option your site may slow down. + Magento\Config\Model\Config\Source\Yesno + + + + validate-digits + + 1 + + + + + Magento\Config\Model\Config\Source\Yesno + + 1 + + + + + + When you enable this option your site may slow down. + Magento\Config\Model\Config\Source\Yesno + + + + + 1 + + + + + Magento\Config\Model\Config\Source\Yesno + When you enable this option your site may slow down. + + 1 + + + + +
+
+
diff --git a/app/code/Magento/AdvancedSearch/etc/config.xml b/app/code/Magento/AdvancedSearch/etc/config.xml new file mode 100644 index 0000000000000..a4affbccdbc4e --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/config.xml @@ -0,0 +1,21 @@ + + + + + + + 1 + 2 + 0 + 1 + 5 + 0 + + + + diff --git a/app/code/Magento/AdvancedSearch/etc/db_schema.xml b/app/code/Magento/AdvancedSearch/etc/db_schema.xml new file mode 100644 index 0000000000000..9fae40411098c --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/db_schema.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + +
+
diff --git a/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json b/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..8addf187744fd --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json @@ -0,0 +1,14 @@ +{ + "catalogsearch_recommendations": { + "column": { + "id": true, + "query_id": true, + "relation_id": true + }, + "constraint": { + "PRIMARY": true, + "CATALOGSEARCH_RECOMMENDATIONS_QUERY_ID_SEARCH_QUERY_QUERY_ID": true, + "CATALOGSEARCH_RECOMMENDATIONS_RELATION_ID_SEARCH_QUERY_QUERY_ID": true + } + } +} \ No newline at end of file diff --git a/app/code/Magento/AdvancedSearch/etc/di.xml b/app/code/Magento/AdvancedSearch/etc/di.xml new file mode 100644 index 0000000000000..21e19fd58825b --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/di.xml @@ -0,0 +1,43 @@ + + + + + + Magento\AdvancedSearch\Model\Recommendations\DataProvider + Related search terms + + + + + Magento\AdvancedSearch\Model\SuggestedQueries + Did you mean + + + + + Magento\CatalogSearch\Model\ResourceModel\EngineInterface::CONFIG_ENGINE_PATH + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + + + + + + Magento\AdvancedSearch\Model\DataProvider\Suggestions + + + + + + + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + + + + + diff --git a/app/code/Magento/AdvancedSearch/etc/module.xml b/app/code/Magento/AdvancedSearch/etc/module.xml new file mode 100644 index 0000000000000..cc0c97f43d542 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/etc/module.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/i18n/en_US.csv b/app/code/Magento/AdvancedSearch/i18n/en_US.csv new file mode 100644 index 0000000000000..f8210d58888ce --- /dev/null +++ b/app/code/Magento/AdvancedSearch/i18n/en_US.csv @@ -0,0 +1,26 @@ +"Related Search Terms","Related Search Terms" +"Add New Search Term","Add New Search Term" +button_label,button_label +"Missing search engine parameter.","Missing search engine parameter." +"Successful! Test again?","Successful! Test again?" +"Connection failed! Test again?","Connection failed! Test again?" +"Enable Search Recommendations","Enable Search Recommendations" +"When you enable this option your site may slow down.","When you enable this option your site may slow down." +"Search Recommendations Count","Search Recommendations Count" +"Show Results Count for Each Recommendation","Show Results Count for Each Recommendation" +"Enable Search Suggestions","Enable Search Suggestions" +"Search Suggestions Count","Search Suggestions Count" +"Show Results Count for Each Suggestion","Show Results Count for Each Suggestion" +"Related search terms","Related search terms" +"Did you mean","Did you mean" +ID,ID +"Search Query","Search Query" +Store,Store +Results,Results +Uses,Uses +"Redirect URL","Redirect URL" +"Suggested Term","Suggested Term" +Yes,Yes +No,No +Action,Action +Edit,Edit diff --git a/app/code/Magento/AdvancedSearch/registration.php b/app/code/Magento/AdvancedSearch/registration.php new file mode 100644 index 0000000000000..c82ffa8e7e4d6 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/registration.php @@ -0,0 +1,9 @@ + + + + + + + + catalog_search_grid + Magento\AdvancedSearch\Model\ResourceModel\Search\Grid\Collection + name + ASC + 1 + 1 + + 1 + + + + + + */*/edit + + getId + + + + + + query_id + query_id_selected + checkbox + query_id_selected + + + + + + ID + query_id + col-id + col-id + + + + + Search Query + query_text + + + + + Store + store_id + store + 1 + 0 + + + + + Results + num_results + number + + + + + Uses + popularity + number + + + + + Redirect URL + redirect + + + + + Suggested Term + 1 + display_in_terms + options + + + 1 + Yes + + + 0 + No + + + + + + + Action + catalog + action + getId + 0 + 0 + + + Edit + + */*/edit + + id + + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_edit.xml b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_edit.xml new file mode 100644 index 0000000000000..5e6774b1b5c6b --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_edit.xml @@ -0,0 +1,28 @@ + + + + + + + + + + search.edit.grid + getSelectedQueries + selected_queries_grid + selected_queries_grid + + + + edit_form + + + + + + diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_relatedgrid.xml b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_relatedgrid.xml new file mode 100644 index 0000000000000..4187ba9127369 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_relatedgrid.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/requirejs-config.js b/app/code/Magento/AdvancedSearch/view/adminhtml/requirejs-config.js new file mode 100644 index 0000000000000..80369c99b8995 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/requirejs-config.js @@ -0,0 +1,12 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + map: { + '*': { + testConnection: 'Magento_AdvancedSearch/js/testconnection' + } + } +}; diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml b/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml new file mode 100644 index 0000000000000..ae202cbfaf442 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml @@ -0,0 +1,15 @@ + + diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js new file mode 100644 index 0000000000000..e28f1b4d07d94 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js @@ -0,0 +1,73 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + 'jquery', + 'Magento_Ui/js/modal/alert', + 'jquery/ui' +], function ($, alert) { + 'use strict'; + + $.widget('mage.testConnection', { + options: { + url: '', + elementId: '', + successText: '', + failedText: '', + fieldMapping: '' + }, + + /** + * Bind handlers to events + */ + _create: function () { + this._on({ + 'click': $.proxy(this._connect, this) + }); + }, + + /** + * Method triggers an AJAX request to check search engine connection + * @private + */ + _connect: function () { + var result = this.options.failedText, + element = $('#' + this.options.elementId), + self = this, + params = {}, + msg = ''; + + element.removeClass('success').addClass('fail'); + $.each($.parseJSON(this.options.fieldMapping), function (key, el) { + params[key] = $('#' + el).val(); + }); + $.ajax({ + url: this.options.url, + showLoader: true, + data: params + }).done(function (response) { + if (response.success) { + element.removeClass('fail').addClass('success'); + result = self.options.successText; + } else { + msg = response.errorMessage; + + if (msg) { + alert({ + content: msg + }); + } + } + }).always(function () { + $('#' + self.options.elementId + '_result').text(result); + }); + } + }); + + return $.mage.testConnection; +}); diff --git a/app/code/Magento/AdvancedSearch/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/AdvancedSearch/view/frontend/layout/catalogsearch_result_index.xml new file mode 100644 index 0000000000000..bf27fe73711e3 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/frontend/layout/catalogsearch_result_index.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml b/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml new file mode 100644 index 0000000000000..6e660555053a1 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml @@ -0,0 +1,27 @@ + +getItems(); +if (count($data)):?> +
+
getTitle()) ?>
+ +
+ escapeHtml($additionalInfo->getQueryText()) ?> + isShowResultsCount()): ?> + getResultsCount() ?> + +
+ +
+ diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorization/LICENSE.txt b/app/code/Magento/Amqp/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorization/LICENSE.txt rename to app/code/Magento/Amqp/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/LICENSE_AFL.txt b/app/code/Magento/Amqp/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Analytics/LICENSE_AFL.txt rename to app/code/Magento/Amqp/LICENSE_AFL.txt diff --git a/app/code/Magento/Amqp/Model/Config.php b/app/code/Magento/Amqp/Model/Config.php new file mode 100644 index 0000000000000..9ec9780317a9f --- /dev/null +++ b/app/code/Magento/Amqp/Model/Config.php @@ -0,0 +1,16 @@ +getPublisherConfig(), + $this->getResponseQueueNameBuilder(), + $communicationConfig, + $rpcConnectionTimeout + ); + } + + /** + * Get publisher config. + * + * @return PublisherConfig + * + * @deprecated 100.2.0 + */ + private function getPublisherConfig() + { + return \Magento\Framework\App\ObjectManager::getInstance()->get(PublisherConfig::class); + } + + /** + * Get response queue name builder. + * + * @return ResponseQueueNameBuilder + * + * @deprecated 100.2.0 + */ + private function getResponseQueueNameBuilder() + { + return \Magento\Framework\App\ObjectManager::getInstance()->get(ResponseQueueNameBuilder::class); + } +} diff --git a/app/code/Magento/Amqp/Model/Queue.php b/app/code/Magento/Amqp/Model/Queue.php new file mode 100644 index 0000000000000..ffef398352bc7 --- /dev/null +++ b/app/code/Magento/Amqp/Model/Queue.php @@ -0,0 +1,16 @@ +get(TopologyConfig::class), + \Magento\Framework\App\ObjectManager::getInstance()->get(ExchangeInstaller::class), + \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigPool::class), + \Magento\Framework\App\ObjectManager::getInstance()->get(QueueInstaller::class), + \Magento\Framework\App\ObjectManager::getInstance()->get(ConnectionTypeResolver::class), + $logger + ); + } +} diff --git a/app/code/Magento/Amqp/README.md b/app/code/Magento/Amqp/README.md new file mode 100644 index 0000000000000..a21624031d619 --- /dev/null +++ b/app/code/Magento/Amqp/README.md @@ -0,0 +1,3 @@ +# Amqp + +**Amqp** provides functionality to publish/consume messages with Amqp. diff --git a/app/code/Magento/Amqp/Setup/ConfigOptionsList.php b/app/code/Magento/Amqp/Setup/ConfigOptionsList.php new file mode 100644 index 0000000000000..7b857dc2bcc2d --- /dev/null +++ b/app/code/Magento/Amqp/Setup/ConfigOptionsList.php @@ -0,0 +1,228 @@ +connectionValidator = $connectionValidator; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return [ + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_HOST, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_HOST, + 'Amqp server host', + self::DEFAULT_AMQP_HOST + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_PORT, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_PORT, + 'Amqp server port', + self::DEFAULT_AMQP_PORT + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_USER, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_USER, + 'Amqp server username', + self::DEFAULT_AMQP_USER + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_PASSWORD, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_PASSWORD, + 'Amqp server password', + self::DEFAULT_AMQP_PASSWORD + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_VIRTUAL_HOST, + 'Amqp virtualhost', + self::DEFAULT_AMQP_VIRTUAL_HOST + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_SSL, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_QUEUE_AMQP_SSL, + 'Amqp SSL', + self::DEFAULT_AMQP_SSL + ), + new TextConfigOption( + self::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS, + TextConfigOption::FRONTEND_WIZARD_TEXTAREA, + self::CONFIG_PATH_QUEUE_AMQP_SSL_OPTIONS, + 'Amqp SSL Options (JSON)', + self::DEFAULT_AMQP_SSL + ), + ]; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function createConfig(array $data, DeploymentConfig $deploymentConfig) + { + $configData = new ConfigData(ConfigFilePool::APP_ENV); + + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_HOST)) { + $configData->set(self::CONFIG_PATH_QUEUE_AMQP_HOST, $data[self::INPUT_KEY_QUEUE_AMQP_HOST]); + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_PORT)) { + $configData->set(self::CONFIG_PATH_QUEUE_AMQP_PORT, $data[self::INPUT_KEY_QUEUE_AMQP_PORT]); + } + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_USER)) { + $configData->set(self::CONFIG_PATH_QUEUE_AMQP_USER, $data[self::INPUT_KEY_QUEUE_AMQP_USER]); + } + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_PASSWORD)) { + $configData->set(self::CONFIG_PATH_QUEUE_AMQP_PASSWORD, $data[self::INPUT_KEY_QUEUE_AMQP_PASSWORD]); + } + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST)) { + $configData->set( + self::CONFIG_PATH_QUEUE_AMQP_VIRTUAL_HOST, + $data[self::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST] + ); + } + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_AMQP_SSL)) { + $configData->set(self::CONFIG_PATH_QUEUE_AMQP_SSL, $data[self::INPUT_KEY_QUEUE_AMQP_SSL]); + } + if (!$this->isDataEmpty( + $data, + self::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS + )) { + $options = json_decode( + $data[self::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS], + true + ); + if ($options !== null) { + $configData->set( + self::CONFIG_PATH_QUEUE_AMQP_SSL_OPTIONS, + $options + ); + } + } + } + + return [$configData]; + } + + /** + * {@inheritdoc} + */ + public function validate(array $options, DeploymentConfig $deploymentConfig) + { + $errors = []; + + if (isset($options[self::INPUT_KEY_QUEUE_AMQP_HOST]) + && $options[self::INPUT_KEY_QUEUE_AMQP_HOST] !== '') { + if (!$this->isDataEmpty( + $options, + self::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS + )) { + $sslOptions = json_decode( + $options[self::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS], + true + ); + } else { + $sslOptions = null; + } + $isSslEnabled = !empty($options[self::INPUT_KEY_QUEUE_AMQP_SSL]) + && $options[self::INPUT_KEY_QUEUE_AMQP_SSL] !== 'false'; + + $result = $this->connectionValidator->isConnectionValid( + $options[self::INPUT_KEY_QUEUE_AMQP_HOST], + $options[self::INPUT_KEY_QUEUE_AMQP_PORT], + $options[self::INPUT_KEY_QUEUE_AMQP_USER], + $options[self::INPUT_KEY_QUEUE_AMQP_PASSWORD], + $options[self::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST], + $isSslEnabled, + $sslOptions + ); + + if (!$result) { + $errors[] = "Could not connect to the Amqp Server."; + } + } + + return $errors; + } + + /** + * Check if data ($data) with key ($key) is empty + * + * @param array $data + * @param string $key + * @return bool + */ + private function isDataEmpty(array $data, $key) + { + if (isset($data[$key]) && $data[$key] !== '') { + return false; + } + + return true; + } +} diff --git a/app/code/Magento/Amqp/Setup/ConnectionValidator.php b/app/code/Magento/Amqp/Setup/ConnectionValidator.php new file mode 100644 index 0000000000000..55a11286c7c43 --- /dev/null +++ b/app/code/Magento/Amqp/Setup/ConnectionValidator.php @@ -0,0 +1,72 @@ +connectionFactory = $connectionFactory; + } + + /** + * Checks Amqp Connection + * + * @param string $host + * @param string $port + * @param string $user + * @param string $password + * @param string $virtualHost + * @param bool $ssl + * @param string[]|null $sslOptions + * @return bool true if the connection succeeded, false otherwise + */ + public function isConnectionValid( + $host, + $port, + $user, + $password = '', + $virtualHost = '', + bool $ssl = false, + array $sslOptions = null + ) { + try { + $options = new FactoryOptions(); + $options->setHost($host); + $options->setPort($port); + $options->setUsername($user); + $options->setPassword($password); + $options->setVirtualHost($virtualHost); + $options->setSslEnabled($ssl); + + if ($sslOptions) { + $options->setSslOptions($sslOptions); + } + + $connection = $this->connectionFactory->create($options); + + $connection->close(); + } catch (\Exception $e) { + return false; + } + + return true; + } +} diff --git a/app/code/Magento/Amqp/Setup/Recurring.php b/app/code/Magento/Amqp/Setup/Recurring.php new file mode 100644 index 0000000000000..cc1951d84e3d0 --- /dev/null +++ b/app/code/Magento/Amqp/Setup/Recurring.php @@ -0,0 +1,38 @@ +topologyInstaller = $topologyInstaller; + } + + /** + * {@inheritdoc} + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $this->topologyInstaller->install(); + } +} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorizenet/LICENSE.txt b/app/code/Magento/Amqp/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorizenet/LICENSE.txt rename to app/code/Magento/Amqp/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorization/LICENSE_AFL.txt b/app/code/Magento/Amqp/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorization/LICENSE_AFL.txt rename to app/code/Magento/Amqp/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Amqp/Test/Mftf/README.md b/app/code/Magento/Amqp/Test/Mftf/README.md new file mode 100644 index 0000000000000..12d1bbc3a4890 --- /dev/null +++ b/app/code/Magento/Amqp/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Amqp Functional Tests + +The Functional Test Module for **Magento Amqp** module. diff --git a/app/code/Magento/Amqp/Test/Unit/Setup/ConfigOptionsListTest.php b/app/code/Magento/Amqp/Test/Unit/Setup/ConfigOptionsListTest.php new file mode 100644 index 0000000000000..5b19ba055d059 --- /dev/null +++ b/app/code/Magento/Amqp/Test/Unit/Setup/ConfigOptionsListTest.php @@ -0,0 +1,234 @@ +options = [ + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_HOST => 'host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PORT => 'port', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_USER => 'user', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PASSWORD => 'password', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST => 'virtual host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL => 'ssl', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS => '{"ssl_option":"test"}', + ]; + + $this->objectManager = new ObjectManager($this); + $this->connectionValidatorMock = $this->getMockBuilder(\Magento\Amqp\Setup\ConnectionValidator::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->model = $this->objectManager->getObject( + \Magento\Amqp\Setup\ConfigOptionsList::class, + [ + 'connectionValidator' => $this->connectionValidatorMock, + ] + ); + } + + public function testGetOptions() + { + $expectedOptions = [ + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_HOST, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_HOST, + 'Amqp server host', + ConfigOptionsList::DEFAULT_AMQP_HOST + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PORT, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_PORT, + 'Amqp server port', + ConfigOptionsList::DEFAULT_AMQP_PORT + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_USER, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_USER, + 'Amqp server username', + ConfigOptionsList::DEFAULT_AMQP_USER + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PASSWORD, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_PASSWORD, + 'Amqp server password', + ConfigOptionsList::DEFAULT_AMQP_PASSWORD + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_VIRTUAL_HOST, + 'Amqp virtualhost', + ConfigOptionsList::DEFAULT_AMQP_VIRTUAL_HOST + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL, + TextConfigOption::FRONTEND_WIZARD_TEXT, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_SSL, + 'Amqp SSL', + ConfigOptionsList::DEFAULT_AMQP_SSL + ), + new TextConfigOption( + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS, + TextConfigOption::FRONTEND_WIZARD_TEXTAREA, + ConfigOptionsList::CONFIG_PATH_QUEUE_AMQP_SSL_OPTIONS, + 'Amqp SSL Options (JSON)', + ConfigOptionsList::DEFAULT_AMQP_SSL + ), + ]; + $this->assertEquals($expectedOptions, $this->model->getOptions()); + } + + /** + * @param array $options + * @param array $expectedConfigData + * @dataProvider getCreateConfigDataProvider + */ + public function testCreateConfig($options, $expectedConfigData) + { + $result = $this->model->createConfig($options, $this->deploymentConfigMock); + $this->assertInternalType('array', $result); + $this->assertNotEmpty($result); + /** @var \Magento\Framework\Config\Data\ConfigData $configData */ + $configData = $result[0]; + $this->assertInstanceOf(\Magento\Framework\Config\Data\ConfigData::class, $configData); + $this->assertEquals($expectedConfigData, $configData->getData()); + } + + public function testValidateInvalidConnection() + { + $expectedResult = ['Could not connect to the Amqp Server.']; + $this->connectionValidatorMock->expects($this->once())->method('isConnectionValid')->willReturn(false); + $this->assertEquals($expectedResult, $this->model->validate($this->options, $this->deploymentConfigMock)); + } + + public function testValidateValidConnection() + { + $expectedResult = []; + $this->connectionValidatorMock->expects($this->once())->method('isConnectionValid')->willReturn(true); + $this->assertEquals($expectedResult, $this->model->validate($this->options, $this->deploymentConfigMock)); + } + + public function testValidateNoOptions() + { + $expectedResult = []; + $options = []; + $this->connectionValidatorMock->expects($this->never())->method('isConnectionValid'); + $this->assertEquals($expectedResult, $this->model->validate($options, $this->deploymentConfigMock)); + } + + /** + * @return array + */ + public function getCreateConfigDataProvider() + { + return [ + [ + [ + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_HOST => 'host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PORT => 'port', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_USER => 'user', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PASSWORD => 'password', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST => 'virtual host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL => 'ssl', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS => '{"ssl_option":"test"}', + ], + ['queue' => + ['amqp' => + [ + 'host' => 'host', + 'port' => 'port', + 'user' => 'user', + 'password' => 'password', + 'virtualhost' => 'virtual host', + 'ssl' => 'ssl', + 'ssl_options' => ['ssl_option' => 'test'], + ] + ] + ], + ], + [ + [ + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_HOST => 'host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PORT => ConfigOptionsList::DEFAULT_AMQP_PORT, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_USER => 'user', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PASSWORD => 'password', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST => 'virtual host', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL => 'ssl', + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL_OPTIONS => '{"ssl_option":"test"}', + ], + ['queue' => + ['amqp' => + [ + 'host' => 'host', + 'port' => ConfigOptionsList::DEFAULT_AMQP_PORT, + 'user' => 'user', + 'password' => 'password', + 'virtualhost' => 'virtual host', + 'ssl' => 'ssl', + 'ssl_options' => ['ssl_option' => 'test'], + ] + ] + ], + ], + [ + [ + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_HOST => ConfigOptionsList::DEFAULT_AMQP_HOST, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PORT => ConfigOptionsList::DEFAULT_AMQP_PORT, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_USER => ConfigOptionsList::DEFAULT_AMQP_USER, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_PASSWORD => ConfigOptionsList::DEFAULT_AMQP_PASSWORD, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_VIRTUAL_HOST => + ConfigOptionsList::DEFAULT_AMQP_VIRTUAL_HOST, + ConfigOptionsList::INPUT_KEY_QUEUE_AMQP_SSL => ConfigOptionsList::DEFAULT_AMQP_SSL, + ], + [], + ], + ]; + } +} diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json new file mode 100644 index 0000000000000..b50e951b46f81 --- /dev/null +++ b/app/code/Magento/Amqp/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-amqp", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "magento/framework": "*", + "magento/framework-amqp": "*", + "magento/framework-message-queue": "*", + "php": "~7.1.3||~7.2.0" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Amqp\\": "" + } + } +} diff --git a/app/code/Magento/Amqp/etc/di.xml b/app/code/Magento/Amqp/etc/di.xml new file mode 100644 index 0000000000000..920bb72261ef9 --- /dev/null +++ b/app/code/Magento/Amqp/etc/di.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + Magento\Framework\MessageQueue\Publisher + + + Magento\Framework\MessageQueue\Rpc\Publisher + + + + + + + + + Magento\Framework\MessageQueue\Bulk\Publisher + + + Magento\Framework\MessageQueue\Bulk\Rpc\Publisher + + + + + + + + Magento\Framework\Amqp\ConnectionTypeResolver + + + + + + + Magento\Framework\Amqp\ExchangeFactory + + + + + + + Magento\Framework\Amqp\Bulk\ExchangeFactory + + + + + + + Magento\Framework\Amqp\QueueFactory + + + + + + \Magento\Framework\Amqp\Bulk\Exchange + + + diff --git a/app/code/Magento/Amqp/etc/module.xml b/app/code/Magento/Amqp/etc/module.xml new file mode 100644 index 0000000000000..1768a9b121c81 --- /dev/null +++ b/app/code/Magento/Amqp/etc/module.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/code/Magento/Amqp/registration.php b/app/code/Magento/Amqp/registration.php new file mode 100644 index 0000000000000..17d8382c698e8 --- /dev/null +++ b/app/code/Magento/Amqp/registration.php @@ -0,0 +1,9 @@ +localeResolver = $localeResolver ?: + ObjectManager::getInstance()->get(\Magento\Framework\Locale\ResolverInterface::class); + parent::__construct($context, $data); + } + + /** + * Add current time zone to comment, properly translated according to locale * * @param \Magento\Framework\Data\Form\Element\AbstractElement $element * @return string @@ -19,7 +41,9 @@ class CollectionTimeLabel extends \Magento\Config\Block\System\Config\Form\Field public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) { $timeZoneCode = $this->_localeDate->getConfigTimezone(); - $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode)->getDisplayName(); + $locale = $this->localeResolver->getLocale(); + $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode) + ->getDisplayName(false, \IntlTimeZone::DISPLAY_LONG, $locale); $element->setData( 'comment', sprintf("%s (%s)", $getLongTimeZoneName, $timeZoneCode) diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php index ff9126a83d59f..87666cb880e54 100644 --- a/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php +++ b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php @@ -5,6 +5,7 @@ */ namespace Magento\Analytics\Controller\Adminhtml\BIEssentials; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Backend\App\Action\Context; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -12,7 +13,7 @@ /** * Provides link to BI Essentials signup */ -class SignUp extends Action +class SignUp extends Action implements HttpGetActionInterface { /** * Path to config value with URL to BI Essentials sign-up page. diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php index cec09377770b0..9068654fa944f 100644 --- a/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php @@ -5,6 +5,7 @@ */ namespace Magento\Analytics\Controller\Adminhtml\Reports; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; use Magento\Analytics\Model\ReportUrlProvider; use Magento\Backend\App\Action; @@ -16,7 +17,7 @@ /** * Provide redirect to resource with reports. */ -class Show extends Action +class Show extends Action implements HttpGetActionInterface { /** * @var ReportUrlProvider diff --git a/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php index e26ad01fc74bf..524062eec35c6 100644 --- a/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php +++ b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php @@ -66,7 +66,9 @@ public function afterSave() $result = preg_match('#(?\d{2}),(?\d{2}),(?\d{2})#', $this->getValue(), $time); if (!$result) { - throw new LocalizedException(__('Time value has an unsupported format')); + throw new LocalizedException( + __('The time value is using an unsupported format. Enter a supported format and try again.') + ); } $cronExprArray = [ diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php index ac97f2a843e61..a5d885c80c3fc 100644 --- a/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php @@ -67,12 +67,7 @@ public function afterSave() try { if ($this->isValueChanged()) { $enabled = $this->getData('value'); - - if ($enabled) { - $this->subscriptionHandler->processEnabled(); - } else { - $this->subscriptionHandler->processDisabled(); - } + $enabled ? $this->subscriptionHandler->processEnabled() : $this->subscriptionHandler->processDisabled(); } } catch (\Exception $e) { $this->_logger->error($e->getMessage()); diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php index 474398fd34e26..ddd9fcba21109 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php @@ -30,4 +30,9 @@ public function toBody(array $data); * @return string */ public function getContentTypeHeader(); + + /** + * @return string + */ + public function getContentMediaType(): string; } diff --git a/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php index 600c192a262bd..059dab554bd92 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Model\Connector\Http; use Magento\Framework\Serialize\Serializer\Json; @@ -14,14 +16,24 @@ class JsonConverter implements ConverterInterface { /** * Content-Type HTTP header for json. + * @deprecated + * @see CONTENT_MEDIA_TYPE */ const CONTENT_TYPE_HEADER = 'Content-Type: application/json'; + /** + * Media-Type corresponding to this converter. + */ + const CONTENT_MEDIA_TYPE = 'application/json'; + /** * @var Json */ private $serializer; + /** + * @param Json $serializer + */ public function __construct(Json $serializer) { $this->serializer = $serializer; @@ -53,6 +65,14 @@ public function toBody(array $data) */ public function getContentTypeHeader() { - return self::CONTENT_TYPE_HEADER; + return sprintf('Content-Type: %s', self::CONTENT_MEDIA_TYPE); + } + + /** + * @inheritdoc + */ + public function getContentMediaType(): string + { + return self::CONTENT_MEDIA_TYPE; } } diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php index ec198e4a3c40b..57b61c1b5562a 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php @@ -38,7 +38,15 @@ public function __construct(ConverterInterface $converter, array $responseHandle public function getResult(\Zend_Http_Response $response) { $result = false; - $responseBody = $this->converter->fromBody($response->getBody()); + $converterMediaType = $this->converter->getContentMediaType(); + + /** Content-Type header may not only contain media-type declaration */ + if ($response->getBody() && is_int(strripos($response->getHeader('Content-Type'), $converterMediaType))) { + $responseBody = $this->converter->fromBody($response->getBody()); + } else { + $responseBody = []; + } + if (array_key_exists($response->getStatus(), $this->responseHandlers)) { $result = $this->responseHandlers[$response->getStatus()]->handleResponse($responseBody); } diff --git a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php index dfa283e10d070..c05357400d075 100644 --- a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php +++ b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php @@ -103,8 +103,9 @@ public function call() if (!$result) { $this->logger->warning( sprintf( - 'Obtaining of an OTP from the MBI service has been failed: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Obtaining of an OTP from the MBI service has been failed: %s. Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php index c1f8152f3134e..e35c9bb42bc43 100644 --- a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php +++ b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php @@ -110,8 +110,10 @@ public function execute() if (!$result) { $this->logger->warning( sprintf( - 'Subscription for MBI service has been failed. An error occurred during token exchange: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Subscription for MBI service has been failed. An error occurred during token exchange: %s.' + . ' Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php index 7b4e452a7b451..59878ff9c0814 100644 --- a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php +++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php @@ -101,8 +101,9 @@ public function execute() if (!$result) { $this->logger->warning( sprintf( - 'Update of the subscription for MBI service has been failed: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Update of the subscription for MBI service has been failed: %s. Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Model/Cryptographer.php b/app/code/Magento/Analytics/Model/Cryptographer.php index 6905eee372ae2..665d564814b14 100644 --- a/app/code/Magento/Analytics/Model/Cryptographer.php +++ b/app/code/Magento/Analytics/Model/Cryptographer.php @@ -56,13 +56,18 @@ public function encode($source) try { $source = (string)$source; } catch (\Exception $e) { - throw new LocalizedException(__('Input data must be string or convertible into string.')); + throw new LocalizedException( + __( + 'The data is invalid. ' + . 'Enter the data as a string or data that can be converted into a string and try again.' + ) + ); } } elseif (!$source) { - throw new LocalizedException(__('Input data must be non-empty string.')); + throw new LocalizedException(__('The data is invalid. Enter the data as a string and try again.')); } if (!$this->validateCipherMethod($this->cipherMethod)) { - throw new LocalizedException(__('Not valid cipher method.')); + throw new LocalizedException(__('The data is invalid. Use a valid cipher method and try again.')); } $initializationVector = $this->getInitializationVector(); @@ -90,7 +95,7 @@ private function getKey() { $token = $this->analyticsToken->getToken(); if (!$token) { - throw new LocalizedException(__('Encryption key can\'t be empty.')); + throw new LocalizedException(__('Enter the encryption key and try again.')); } return hash('sha256', $token); } diff --git a/app/code/Magento/Analytics/Model/ExportDataHandler.php b/app/code/Magento/Analytics/Model/ExportDataHandler.php index b9d3b6340184b..dc17a548763eb 100644 --- a/app/code/Magento/Analytics/Model/ExportDataHandler.php +++ b/app/code/Magento/Analytics/Model/ExportDataHandler.php @@ -195,7 +195,7 @@ private function pack($source, $destination) private function validateSource(WriteInterface $directory, $path) { if (!$directory->isExist($path)) { - throw new LocalizedException(__('Source "%1" is not exist', $directory->getAbsolutePath($path))); + throw new LocalizedException(__('The "%1" source doesn\'t exist.', $directory->getAbsolutePath($path))); } return $directory->getAbsolutePath($path); diff --git a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php index 81ee9b15781d1..b4b7adebf7459 100644 --- a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php @@ -11,6 +11,7 @@ /** * Responsible for Select object creation, works as a builder. Returns Select as result; + * * Used in SQL assemblers. */ class SelectBuilder @@ -85,11 +86,13 @@ public function getJoins() * Set joins conditions * * @param array $joins - * @return void + * @return $this */ public function setJoins($joins) { $this->joins = $joins; + + return $this; } /** @@ -106,11 +109,13 @@ public function getConnectionName() * Set connection name * * @param string $connectionName - * @return void + * @return $this */ public function setConnectionName($connectionName) { $this->connectionName = $connectionName; + + return $this; } /** @@ -127,11 +132,13 @@ public function getColumns() * Set columns * * @param array $columns - * @return void + * @return $this */ public function setColumns($columns) { $this->columns = $columns; + + return $this; } /** @@ -148,11 +155,13 @@ public function getFilters() * Set filters * * @param array $filters - * @return void + * @return $this */ public function setFilters($filters) { $this->filters = $filters; + + return $this; } /** @@ -169,11 +178,13 @@ public function getFrom() * Set from condition * * @param array $from - * @return void + * @return $this */ public function setFrom($from) { $this->from = $from; + + return $this; } /** @@ -236,11 +247,13 @@ public function getGroup() * Set group * * @param array $group - * @return void + * @return $this */ public function setGroup($group) { $this->group = $group; + + return $this; } /** @@ -257,11 +270,13 @@ public function getParams() * Set parameters * * @param array $params - * @return void + * @return $this */ public function setParams($params) { $this->params = $params; + + return $this; } /** @@ -278,10 +293,12 @@ public function getHaving() * Set having condition * * @param array $having - * @return void + * @return $this */ public function setHaving($having) { $this->having = $having; + + return $this; } } diff --git a/app/code/Magento/Analytics/ReportXml/ReportProvider.php b/app/code/Magento/Analytics/ReportXml/ReportProvider.php index 60e722930c244..8966d018dc6b9 100644 --- a/app/code/Magento/Analytics/ReportXml/ReportProvider.php +++ b/app/code/Magento/Analytics/ReportXml/ReportProvider.php @@ -55,7 +55,7 @@ public function __construct( private function getIteratorName(Query $query) { $config = $query->getConfig(); - return isset($config['iterator']) ? $config['iterator'] : null; + return $config['iterator'] ?? null; } /** diff --git a/app/code/Magento/Analytics/Setup/InstallData.php b/app/code/Magento/Analytics/Setup/InstallData.php deleted file mode 100644 index 9832849bacc04..0000000000000 --- a/app/code/Magento/Analytics/Setup/InstallData.php +++ /dev/null @@ -1,52 +0,0 @@ -getConnection()->insertMultiple( - $setup->getTable('core_config_data'), - [ - [ - 'scope' => 'default', - 'scope_id' => 0, - 'path' => 'analytics/subscription/enabled', - 'value' => 1 - ], - [ - 'scope' => 'default', - 'scope_id' => 0, - 'path' => SubscriptionHandler::CRON_STRING_PATH, - 'value' => join(' ', SubscriptionHandler::CRON_EXPR_ARRAY) - ] - ] - ); - - $setup->getConnection()->insert( - $setup->getTable('flag'), - [ - 'flag_code' => SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, - 'state' => 0, - 'flag_data' => 24, - ] - ); - } -} diff --git a/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php b/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php new file mode 100644 index 0000000000000..a352854a8b77b --- /dev/null +++ b/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php @@ -0,0 +1,92 @@ +moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->insertMultiple( + $this->moduleDataSetup->getTable('core_config_data'), + [ + [ + 'scope' => 'default', + 'scope_id' => 0, + 'path' => 'analytics/subscription/enabled', + 'value' => 1 + ], + [ + 'scope' => 'default', + 'scope_id' => 0, + 'path' => SubscriptionHandler::CRON_STRING_PATH, + 'value' => join(' ', SubscriptionHandler::CRON_EXPR_ARRAY) + ] + ] + ); + + $this->moduleDataSetup->getConnection()->insert( + $this->moduleDataSetup->getTable('flag'), + [ + 'flag_code' => SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, + 'state' => 0, + 'flag_data' => 24, + ] + ); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public static function getVersion() + { + return '2.0.0'; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml new file mode 100644 index 0000000000000..83f27def4b4e8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml @@ -0,0 +1,33 @@ + + + + + + noreport + No + Report + noreport@example.com + 123123q + 123123q + en_US + true + 123123q + + + restrictedWebUser + restricted + webUser + restrictedWebUser@example.com + 123123q + 123123q + en_US + true + 123123q + + diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml new file mode 100644 index 0000000000000..099cc71321b84 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml @@ -0,0 +1,171 @@ + + + + + + noreport + 123123q + + Magento_Backend::dashboard + Magento_Sales::sales + Magento_Sales::sales_operation + Magento_Sales::sales_order + Magento_Sales::actions + Magento_Sales::create + Magento_Sales::actions_view + Magento_Sales::email + Magento_Sales::reorder + Magento_Sales::actions_edit + Magento_Sales::cancel + Magento_Sales::review_payment + Magento_Sales::capture + Magento_Sales::invoice + Magento_Sales::creditmemo + Magento_Sales::hold + Magento_Sales::unhold + Magento_Sales::ship + Magento_Sales::comment + Magento_Sales::emails + Magento_Sales::sales_invoice + Magento_Sales::shipment + Magento_Sales::sales_creditmemo + Magento_Paypal::billing_agreement + Magento_Paypal::billing_agreement_actions + Magento_Paypal::billing_agreement_actions_view + Magento_Paypal::actions_manage + Magento_Paypal::use + Magento_Sales::transactions + Magento_Sales::transactions_fetch + Magento_Catalog::catalog + Magento_Catalog::catalog_inventory + Magento_Catalog::products + Magento_Catalog::categories + Magento_Customer::customer + Magento_Customer::manage + Magento_Customer::online + Magento_Cart::cart + Magento_Cart::manage + Magento_Backend::myaccount + Magento_Backend::marketing + Magento_CatalogRule::promo + Magento_CatalogRule::promo_catalog + Magento_SalesRule::quote + Magento_Backend::marketing_communications + Magento_Email::template + Magento_Newsletter::template + Magento_Newsletter::queue + Magento_Newsletter::subscriber + Magento_Backend::marketing_seo + Magento_Search::search + Magento_Search::synonyms + Magento_UrlRewrite::urlrewrite + Magento_Sitemap::sitemap + Magento_Backend::marketing_user_content + Magento_Review::reviews_all + Magento_Review::pending + Magento_Backend::content + Magento_Backend::content_elements + Magento_Cms::page + Magento_Cms::save + Magento_Cms::page_delete + Magento_Cms::block + Magento_Widget::widget_instance + Magento_Cms::media_gallery + Magento_Backend::design + Magento_Theme::theme + Magento_Backend::schedule + Magento_Backend::content_translation + Magento_Backend::stores + Magento_Backend::stores_settings + Magento_Backend::store + Magento_Config::config + Magento_Payment::payment + Magento_Cms::config_cms + Magento_GoogleAnalytics::google + Magento_Downloadable::downloadable + Magento_Contact::contact + Magento_CatalogInventory::cataloginventory + Magento_Payment::payment_services + Magento_Newsletter::newsletter + Magento_Catalog::config_catalog + Magento_CatalogSearch::config_catalog_search + Magento_Shipping::config_shipping + Magento_Shipping::shipping_policy + Magento_Shipping::carriers + Magento_Multishipping::config_multishipping + Magento_Config::config_general + Magento_Config::web + Magento_Config::config_design + Magento_Paypal::paypal + Magento_Customer::config_customer + Magento_Tax::config_tax + Magento_Checkout::checkout + Magento_Persistent::persistent + Magento_Sales::config_sales + Magento_Sales::sales_email + Magento_Sales::sales_pdf + Magento_Reports::reports + Magento_Sitemap::config_sitemap + Magento_Wishlist::config_wishlist + Magento_Config::config_system + Magento_SalesRule::config_promo + Magento_Config::advanced + Magento_Config::config_admin + Magento_Config::trans_email + Magento_Config::dev + Magento_Config::currency + Magento_Rss::rss + Magento_Config::sendfriend + Magento_NewRelicReporting::config_newrelicreporting + Magento_CheckoutAgreements::checkoutagreement + Magento_Sales::order_statuses + Magento_Tax::manage_tax + Magento_CurrencySymbol::system_currency + Magento_CurrencySymbol::currency_rates + Magento_CurrencySymbol::symbols + Magento_Backend::stores_attributes + Magento_Catalog::attributes_attributes + Magento_Catalog::update_attributes + Magento_Catalog::sets + Magento_Review::ratings + Magento_Swatches::iframe + Magento_Backend::stores_other_settings + Magento_Customer::group + Magento_Backend::system + Magento_Backend::convert + Magento_ImportExport::import + Magento_ImportExport::export + Magento_TaxImportExport::import_export + Magento_ImportExport::history + Magento_Backend::extensions + Magento_Backend::local + Magento_Backend::custom + Magento_Backend::tools + Magento_Backend::cache + Magento_Backend::setup_wizard + Magento_Backup::backup + Magento_Backup::rollback + Magento_Indexer::index + Magento_Indexer::changeMode + Magento_User::acl + Magento_User::acl_users + Magento_User::locks + Magento_User::acl_roles + Magento_Backend::system_other_settings + Magento_AdminNotification::adminnotification + Magento_AdminNotification::show_toolbar + Magento_AdminNotification::show_list + Magento_AdminNotification::mark_as_read + Magento_AdminNotification::adminnotification_remove + Magento_Variable::variable + Magento_EncryptionKey::crypt_key + Magento_Backend::global_search + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/LICENSE.txt b/app/code/Magento/Analytics/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/LICENSE.txt rename to app/code/Magento/Analytics/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorizenet/LICENSE_AFL.txt b/app/code/Magento/Analytics/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Authorizenet/LICENSE_AFL.txt rename to app/code/Magento/Analytics/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Analytics/Test/Mftf/README.md b/app/code/Magento/Analytics/Test/Mftf/README.md new file mode 100644 index 0000000000000..cdeb48941e6a4 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Analytics Functional Tests + +The Functional Test Module for **Magento Analytics** module. diff --git a/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml new file mode 100644 index 0000000000000..f8554a4ea115b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml @@ -0,0 +1,22 @@ + + + +
+ + + + + + + + + + + +
+
\ No newline at end of file diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml new file mode 100644 index 0000000000000..ff89ca9b663ee --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + <description value="An admin user cannot save a blank industry setting on the Advanced Reporting configuration page."/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-63981"/> + <group value="analytics"/> + </annotations> + <after> + <amOnPage stepKey="amOnLogoutPage" url="admin/admin/auth/logout/"/> + </after> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <amOnPage stepKey="amOnAdminConfig" url="{{AdminConfigPage.url}}"/> + <waitForPageLoad stepKey="waitForAdminConfig"/> + <click stepKey="clickAdvancedReportingConfigMenu" selector="{{AdminConfigSection.advancedReportingMenuItem}}"/> + <see stepKey="seeAdvancedReportingIndustryLabel" selector="{{AdminConfigSection.advancedReportingIndustryLabel}}" userInput="Industry"/> + <selectOption stepKey="selectAdvancedReportingIndustry" selector="{{AdminConfigSection.advancedReportingIndustry}}" userInput="--Please Select--"/> + <click stepKey="clickSaveConfigButton" selector="{{AdminConfigSection.saveButton}}"/> + <see stepKey="seeBlankIndustryErrorMessage" selector="{{AdminConfigSection.advancedReportingBlankIndustryError}}" userInput="Please select an industry."/> + </test> +</tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml new file mode 100644 index 0000000000000..1706383fc7866 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -0,0 +1,44 @@ +<?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="AdminConfigurationEnableDisableAnalyticsTest"> + <annotations> + <features value="Analytics"/> + <stories value="Enable/disable Advanced Reporting"/> + <title value="Enable Disable Advanced Reporting"/> + <description value="An admin user can enable/disable Advanced Reporting."/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-66465"/> + <group value="analytics"/> + </annotations> + <after> + <amOnPage stepKey="amOnLogoutPage" url="admin/admin/auth/logout/"/> + </after> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <!--Goto admin stores configure page --> + <amOnPage stepKey="amOnAdminConfig" url="{{AdminConfigPage.url}}"/> + <!--Enable Advanced Reporting--> + <click stepKey="clickAdvancedReportingConfigMenu" selector="{{AdminConfigSection.advancedReportingMenuItem}}"/> + <see stepKey="seeAdvancedReportingServiceLabelEnabled" selector="{{AdminConfigSection.advancedReportingServiceLabel}}" userInput="Advanced Reporting Service"/> + <selectOption stepKey="selectAdvancedReportingServiceEnabled" selector="{{AdminConfigSection.advancedReportingService}}" userInput="Enable"/> + <see stepKey="seeAdvancedReportingIndustryLabel" selector="{{AdminConfigSection.advancedReportingIndustryLabel}}" userInput="Industry"/> + <selectOption stepKey="selectAdvancedReportingIndustry" selector="{{AdminConfigSection.advancedReportingIndustry}}" userInput="Apps and Games"/> + <click stepKey="clickSaveConfigButtonEnabled" selector="{{AdminConfigSection.saveButton}}"/> + <see stepKey="seeSaveConfigurationMessageEnabled" selector="{{AdminConfigSection.advancedReportingSuccessMessage}}" userInput="You saved the configuration."/> + <see stepKey="seeAdvancedReportingServiceEnabled" selector="{{AdminConfigSection.advancedReportingService}}" userInput="Enable"/> + <see stepKey="seeAdvancedReportingServiceStatusEnabled" selector="{{AdminConfigSection.advancedReportingServiceStatus}}" userInput="Subscription status: Pending"/> + <!--Disable Advanced Reporting--> + <see stepKey="seeAdvancedReportingServiceLabelDisabled" selector="{{AdminConfigSection.advancedReportingServiceLabel}}" userInput="Advanced Reporting Service"/> + <selectOption stepKey="selectAdvancedReportingServiceDisabled" selector="{{AdminConfigSection.advancedReportingService}}" userInput="Disable"/> + <click stepKey="clickSaveConfigButtonDisabled" selector="{{AdminConfigSection.saveButton}}"/> + <see stepKey="seeSaveConfigurationMessageDisabled" selector="{{AdminConfigSection.advancedReportingSuccessMessage}}" userInput="You saved the configuration."/> + <see stepKey="seeAdvancedReportingServiceDisabled" selector="{{AdminConfigSection.advancedReportingService}}" userInput="Disable"/> + <see stepKey="seeAdvancedReportingServiceStatusDisabled" selector="{{AdminConfigSection.advancedReportingServiceStatus}}" userInput="Subscription status: Disabled"/> + </test> +</tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml new file mode 100644 index 0000000000000..dcfdca9e8edd7 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -0,0 +1,31 @@ +<?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="AdminConfigurationIndustryTest"> + <annotations> + <features value="Analytics"/> + <stories value="Set Magento Advanced reporting industry"/> + <title value="Set Magento Advanced reporting industry"/> + <description value="An admin user can change the industry setting on the Advanced Reporting configuration page."/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-63898"/> + <group value="analytics"/> + </annotations> + + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + + <amOnPage stepKey="amOnAdminConfig" url="{{AdminConfigPage.url}}"/> + <click stepKey="clickAdvancedReportingConfigMenu" selector="{{AdminConfigSection.advancedReportingMenuItem}}"/> + <see stepKey="seeAdvancedReportingIndustryLabel" selector="{{AdminConfigSection.advancedReportingIndustryLabel}}" userInput="Industry"/> + <selectOption stepKey="selectAdvancedReportingIndustry" selector="{{AdminConfigSection.advancedReportingIndustry}}" userInput="Apps and Games"/> + <click stepKey="clickSaveConfigButton" selector="{{AdminConfigSection.saveButton}}"/> + <see stepKey="seeIndustrySuccessMessage" selector="{{AdminConfigSection.advancedReportingSuccessMessage}}" userInput="You saved the configuration."/> + </test> +</tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml new file mode 100644 index 0000000000000..5414e9c2a5c18 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml @@ -0,0 +1,62 @@ +<?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="AdminConfigurationPermissionTest"> + <annotations> + <features value="Analytics"/> + <stories value="Advanced Reporting configuration permission"/> + <title value="Advanced Reporting configuration permission"/> + <description value="An admin user without Analytics permissions should not be able to see the Advanced Reporting configuration page."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-82648"/> + <group value="analytics"/> + </annotations> + <before> + <createData stepKey="noReportUserRole" entity="adminNoReportRole"/> + <createData stepKey="noReportUser" entity="adminNoReport"/> + </before> + <after> + <amOnPage stepKey="amOnLogoutPage" url="admin/admin/auth/logout/"/> + </after> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + + <amOnPage stepKey="amOnAdminUsersPage" url="{{AdminUsersPage.url}}"/> + <fillField stepKey="fillUsernameSearch" selector="{{AdminUserGridSection.usernameFilterTextField}}" userInput="$$noReportUser.username$$"/> + <click stepKey="clickSearchButton" selector="{{AdminUserGridSection.searchButton}}"/> + <waitForPageLoad stepKey="wait1" time="10"/> + <see stepKey="seeFoundUsername" selector="{{AdminUserGridSection.usernameInFirstRow}}" userInput="$$noReportUser.username$$"/> + <click stepKey="clickFoundUsername" selector="{{AdminUserGridSection.searchResultFirstRow}}"/> + <waitForPageLoad stepKey="wait2" time="30"/> + <seeInField stepKey="seeUsernameInField" selector="{{AdminEditUserSection.usernameTextField}}" userInput="$$noReportUser.username$$"/> + <fillField stepKey="fillCurrentPassword" selector="{{AdminEditUserSection.currentPasswordField}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click stepKey="clickUserRoleTab" selector="{{AdminEditUserSection.userRoleTab}}"/> + + <fillField stepKey="fillRoleNameSearch" selector="{{AdminEditUserSection.roleNameFilterTextField}}" userInput="$$noReportUserRole.rolename$$"/> + <click stepKey="clickSearchButtonUserRole" selector="{{AdminEditUserSection.searchButton}}"/> + <waitForPageLoad stepKey="wait3" time="10"/> + <see stepKey="seeFoundRoleName" selector="{{AdminEditUserSection.roleNameInFirstRow}}" userInput="$$noReportUserRole.rolename$$"/> + <click stepKey="clickFoundRoleName" selector="{{AdminEditUserSection.searchResultFirstRow}}"/> + <click stepKey="clickSaveButton" selector="{{AdminEditUserSection.saveButton}}"/> + <waitForPageLoad stepKey="wait4" time="10"/> + <see stepKey="saveUserSuccessMessage" selector="{{AdminUserGridSection.successMessage}}" userInput="You saved the user."/> + + <amOnPage stepKey="amOnAdminConfig" url="{{AdminConfigPage.url}}"/> + <see stepKey="seeAdvancedReportingConfigMenuItem" selector="{{AdminConfigSection.advancedReportingMenuItem}}" userInput="Advanced Reporting"/> + <amOnPage stepKey="amOnLogoutPage2" url="admin/admin/auth/logout/"/> + + <amOnPage stepKey="amOnAdminLoginPage" url="{{AdminLoginPage.url}}"/> + <fillField stepKey="fillUsernameNoReport" selector="{{AdminLoginFormSection.username}}" userInput="$$noReportUser.username$$"/> + <fillField stepKey="fillPasswordNoReport" selector="{{AdminLoginFormSection.password}}" userInput="$$noReportUser.password$$"/> + <click stepKey="clickOnSignIn2" selector="{{AdminLoginFormSection.signIn}}"/> + <waitForPageLoad stepKey="wait5" time="10"/> + <amOnPage stepKey="amOnAdminConfig2" url="{{AdminConfigPage.url}}"/> + <dontSee stepKey="dontSeeAdvancedReportingConfigMenuItem" selector="{{AdminConfigSection.advancedReportingMenuItem}}" userInput="Advanced Reporting"/> + </test> +</tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml new file mode 100644 index 0000000000000..3f17df108b50b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -0,0 +1,35 @@ +<?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="AdminConfigurationTimeToSendDataTest"> + <annotations> + <features value="Analytics"/> + <stories value="Time of the day to collect data"/> + <title value="Time of the day to collect data"/> + <description value="An admin user can change the time of the day to collect data setting on the Advanced Reporting configuration page."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-66464"/> + <group value="analytics"/> + </annotations> + <after> + <amOnPage stepKey="amOnLogoutPage" url="admin/admin/auth/logout/"/> + </after> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <amOnPage stepKey="amOnAdminConfig" url="{{AdminConfigPage.url}}"/> + <waitForPageLoad stepKey="waitForAdminConfig"/> + <click stepKey="clickAdvancedReportingConfigMenu" selector="{{AdminConfigSection.advancedReportingMenuItem}}"/> + <selectOption stepKey="selectAdvancedReportingIndustry" selector="{{AdminConfigSection.advancedReportingIndustry}}" userInput="Apps and Games"/> + <selectOption stepKey="selectAdvancedReportingHour" selector="{{AdminConfigSection.advancedReportingHour}}" userInput="11"/> + <selectOption stepKey="selectAdvancedReportingMinute" selector="{{AdminConfigSection.advancedReportingMinute}}" userInput="11"/> + <selectOption stepKey="selectAdvancedReportingSeconds" selector="{{AdminConfigSection.advancedReportingSeconds}}" userInput="00"/> + <click stepKey="clickSaveConfigButton" selector="{{AdminConfigSection.saveButton}}"/> + <see stepKey="seeBlankIndustryErrorMessage" selector="{{AdminConfigSection.advancedReportingSuccessMessage}}" userInput="You saved the configuration."/> + </test> +</tests> diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php index cbf06264096ac..407e323aeaae6 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php @@ -65,11 +65,11 @@ public function testRender() ->method('getLabel') ->willReturn('Comment label'); $html = $this->additionalComment->render($this->abstractElementMock); - $this->assertRegexp( + $this->assertRegExp( "/New comment/", $html ); - $this->assertRegexp( + $this->assertRegExp( "/Comment label/", $html ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php index a652cf6b3d548..d567d65882350 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php @@ -9,6 +9,7 @@ use Magento\Backend\Block\Template\Context; use Magento\Framework\Data\Form; use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -34,6 +35,11 @@ class CollectionTimeLabelTest extends \PHPUnit\Framework\TestCase */ private $abstractElementMock; + /** + * @var ResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $localeResolver; + protected function setUp() { $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) @@ -53,12 +59,17 @@ protected function setUp() $this->contextMock->expects($this->any()) ->method('getLocaleDate') ->willReturn($this->timeZoneMock); + $this->localeResolver = $this->getMockBuilder(ResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getLocale']) + ->getMockForAbstractClass(); $objectManager = new ObjectManager($this); $this->collectionTimeLabel = $objectManager->getObject( CollectionTimeLabel::class, [ - 'context' => $this->contextMock + 'context' => $this->contextMock, + 'localeResolver' => $this->localeResolver ] ); } @@ -73,7 +84,10 @@ public function testRender() $this->abstractElementMock->expects($this->any()) ->method('getComment') ->willReturn('Eastern Standard Time (America/New_York)'); - $this->assertRegexp( + $this->localeResolver->expects($this->once()) + ->method('getLocale') + ->willReturn('en_US'); + $this->assertRegExp( "/Eastern Standard Time \(America\/New_York\)/", $this->collectionTimeLabel->render($this->abstractElementMock) ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php index d643bc05cc615..78ff581f3de9d 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php @@ -74,7 +74,7 @@ public function testRender() $this->abstractElementMock->expects($this->any()) ->method('getComment') ->willReturn('Subscription status: Enabled'); - $this->assertRegexp( + $this->assertRegExp( "/Subscription status: Enabled/", $this->subscriptionStatusLabel->render($this->abstractElementMock) ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php index abce48c36c86a..6a0cecc781062 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php @@ -65,7 +65,7 @@ public function testRender() ->method('getHint') ->willReturn('New hint'); $html = $this->vertical->render($this->abstractElementMock); - $this->assertRegexp( + $this->assertRegExp( "/New comment/", $html ); diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php index 5ee59a7913a61..92f79c2bf6dee 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php @@ -12,6 +12,7 @@ /** * A unit test for testing of the CURL HTTP client. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CurlTest extends \PHPUnit\Framework\TestCase { @@ -97,7 +98,6 @@ public function getTestData() 'version' => '1.1', 'body'=> ['name' => 'value'], 'url' => 'http://www.mystore.com', - 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], 'method' => \Magento\Framework\HTTP\ZendClient::POST, ] ] @@ -105,7 +105,9 @@ public function getTestData() } /** + * @param array $data * @return void + * @throws \Zend_Http_Exception * @dataProvider getTestData */ public function testRequestSuccess(array $data) @@ -118,7 +120,7 @@ public function testRequestSuccess(array $data) $data['method'], $data['url'], $data['version'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], json_encode($data['body']) ); $this->curlAdapterMock->expects($this->once()) @@ -139,14 +141,16 @@ public function testRequestSuccess(array $data) $data['method'], $data['url'], $data['body'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], $data['version'] ) ); } /** + * @param array $data * @return void + * @throws \Zend_Http_Exception * @dataProvider getTestData */ public function testRequestError(array $data) @@ -158,7 +162,7 @@ public function testRequestError(array $data) $data['method'], $data['url'], $data['version'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], json_encode($data['body']) ); $this->curlAdapterMock->expects($this->once()) @@ -184,7 +188,7 @@ public function testRequestError(array $data) $data['method'], $data['url'], $data['body'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], $data['version'] ) ); @@ -195,14 +199,13 @@ public function testRequestError(array $data) */ private function createJsonConverter() { - $converterMock = $this->getMockBuilder(ConverterInterface::class) - ->getMockForAbstractClass(); + $converterMock = $this->getMockBuilder(JsonConverter::class) + ->setMethodsExcept(['getContentTypeHeader']) + ->disableOriginalConstructor() + ->getMock(); $converterMock->expects($this->any())->method('toBody')->willReturnCallback(function ($value) { return json_encode($value); }); - $converterMock->expects($this->any()) - ->method('getContentTypeHeader') - ->willReturn(JsonConverter::CONTENT_TYPE_HEADER); return $converterMock; } } diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php index 5ad8eebfc7ad3..d3258c8ae9caa 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php @@ -25,6 +25,9 @@ class JsonConverterTest extends \PHPUnit\Framework\TestCase */ private $converter; + /** + * @return void + */ protected function setUp() { $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -37,9 +40,15 @@ protected function setUp() ); } + /** + * @return void + */ public function testConverterContainsHeader() { - $this->assertEquals(JsonConverter::CONTENT_TYPE_HEADER, $this->converter->getContentTypeHeader()); + $this->assertEquals( + 'Content-Type: ' . JsonConverter::CONTENT_MEDIA_TYPE, + $this->converter->getContentTypeHeader() + ); } /** @@ -55,6 +64,9 @@ public function testConvertBody($unserializedResult, $expected) $this->assertEquals($expected, $this->converter->fromBody('body')); } + /** + * @return array + */ public function convertBodyDataProvider() { return [ @@ -63,6 +75,9 @@ public function convertBodyDataProvider() ]; } + /** + * return void + */ public function testConvertData() { $this->serializerMock->expects($this->once()) diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php index 3d4c90bcd07f7..2564240c4fa11 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php @@ -3,49 +3,115 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Test\Unit\Model\Connector\Http; -use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\Http\ConverterInterface; use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; use Magento\Analytics\Model\Connector\Http\ResponseResolver; -use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; class ResponseResolverTest extends \PHPUnit\Framework\TestCase { - public function testGetResultHandleResponseSuccess() + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ConverterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $converterMock; + + /** + * @var ResponseHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $successResponseHandlerMock; + + /** + * @var ResponseHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $notFoundResponseHandlerMock; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * @return void + */ + protected function setUp() { - $expectedBody = ['test' => 'testValue']; - $response = new \Zend_Http_Response(201, [], json_encode($expectedBody)); - $responseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) - ->getMockForAbstractClass(); - $responseHandlerMock->expects($this->once()) - ->method('handleResponse') - ->with($expectedBody) - ->willReturn(true); - $notFoundResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) - ->getMockForAbstractClass(); - $notFoundResponseHandlerMock->expects($this->never())->method('handleResponse'); - $serializerMock = $this->getMockBuilder(Json::class) + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->converterMock = $this->getMockBuilder(ConverterInterface::class) ->disableOriginalConstructor() ->getMock(); - $serializerMock->expects($this->once()) - ->method('unserialize') - ->willReturn($expectedBody); - $objectManager = new ObjectManager($this); - $responseResolver = $objectManager->getObject( + $this->successResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $this->notFoundResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $this->responseResolver = $this->objectManagerHelper->getObject( ResponseResolver::class, [ - 'converter' => $objectManager->getObject( - JsonConverter::class, - ['serializer' => $serializerMock] - ), + 'converter' => $this->converterMock, 'responseHandlers' => [ - 201 => $responseHandlerMock, - 404 => $notFoundResponseHandlerMock, + 201 => $this->successResponseHandlerMock, + 404 => $this->notFoundResponseHandlerMock, ] ] ); - $this->assertTrue($responseResolver->getResult($response)); + } + + /** + * @return void + * @throws \Zend_Http_Exception + */ + public function testGetResultHandleResponseSuccess() + { + $expectedBody = ['test' => 'testValue']; + $response = new \Zend_Http_Response(201, ['Content-Type' => 'application/json'], json_encode($expectedBody)); + $this->converterMock + ->method('getContentMediaType') + ->willReturn('application/json'); + + $this->successResponseHandlerMock + ->expects($this->once()) + ->method('handleResponse') + ->with($expectedBody) + ->willReturn(true); + $this->notFoundResponseHandlerMock + ->expects($this->never()) + ->method('handleResponse'); + $this->converterMock + ->method('fromBody') + ->willReturn($expectedBody); + $this->assertTrue($this->responseResolver->getResult($response)); + } + + /** + * @return void + * @throws \Zend_Http_Exception + */ + public function testGetResultHandleResponseUnexpectedContentType() + { + $expectedBody = 'testString'; + $response = new \Zend_Http_Response(201, ['Content-Type' => 'plain/text'], $expectedBody); + $this->converterMock + ->method('getContentMediaType') + ->willReturn('application/json'); + $this->converterMock + ->expects($this->never()) + ->method('fromBody'); + $this->successResponseHandlerMock + ->expects($this->once()) + ->method('handleResponse') + ->with([]) + ->willReturn(false); + $this->notFoundResponseHandlerMock + ->expects($this->never()) + ->method('handleResponse'); + $this->assertFalse($this->responseResolver->getResult($response)); } } diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php index db6cda7153c1a..c113b2dc275dd 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php @@ -57,6 +57,9 @@ class SignUpCommandTest extends \PHPUnit\Framework\TestCase */ private $responseResolverMock; + /** + * @return void + */ protected function setUp() { $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) @@ -91,6 +94,10 @@ protected function setUp() ); } + /** + * @throws \Zend_Http_Exception + * @return void + */ public function testExecuteSuccess() { $this->integrationManagerMock->expects($this->once()) @@ -124,6 +131,9 @@ public function testExecuteSuccess() $this->assertTrue($this->signUpCommand->execute()); } + /** + * @return void + */ public function testExecuteFailureCannotGenerateToken() { $this->integrationManagerMock->expects($this->once()) @@ -134,6 +144,10 @@ public function testExecuteFailureCannotGenerateToken() $this->assertFalse($this->signUpCommand->execute()); } + /** + * @throws \Zend_Http_Exception + * @return void + */ public function testExecuteFailureResponseIsEmpty() { $this->integrationManagerMock->expects($this->once()) @@ -163,7 +177,6 @@ private function getTestData() 'url' => 'http://www.mystore.com', 'access-token' => 'thisisaccesstoken', 'integration-token' => 'thisisintegrationtoken', - 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], 'method' => \Magento\Framework\HTTP\ZendClient::POST, 'body'=> ['token' => 'thisisintegrationtoken','url' => 'http://www.mystore.com'], ]; diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php index d7dcf50620550..ac141fae4be66 100644 --- a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php @@ -79,9 +79,9 @@ protected function setUp() * @dataProvider errorDataProvider * @param string $reportName * @param array $result - * @param \PHPUnit_Framework_MockObject_Stub $queryReturnStub + * @param \PHPUnit\Framework\MockObject\Stub $queryReturnStub */ - public function testValidate($reportName, $result, \PHPUnit_Framework_MockObject_Stub $queryReturnStub) + public function testValidate($reportName, $result, \PHPUnit\Framework\MockObject\Stub $queryReturnStub) { $connectionName = 'testConnection'; $this->queryFactoryMock->expects($this->once()) diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php index 8be2f0ee968ef..a4362d583dfbc 100644 --- a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php @@ -64,12 +64,12 @@ public function testCreate() ['link-type' => 'right', 'table' => 'attribute', 'condition' => 'neq'], ]; $groups = ['id', 'name']; - $this->selectBuilder->setConnectionName($connectionName); - $this->selectBuilder->setFrom($from); - $this->selectBuilder->setColumns($columns); - $this->selectBuilder->setFilters([$filter]); - $this->selectBuilder->setJoins($joins); - $this->selectBuilder->setGroup($groups); + $this->selectBuilder->setConnectionName($connectionName) + ->setFrom($from) + ->setColumns($columns) + ->setFilters([$filter]) + ->setJoins($joins) + ->setGroup($groups); $this->resourceConnectionMock->expects($this->once()) ->method('getConnection') ->with($connectionName) diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json index bdea53c445a34..88127f3c62a92 100644 --- a/app/code/Magento/Analytics/composer.json +++ b/app/code/Magento/Analytics/composer.json @@ -2,15 +2,14 @@ "name": "magento/module-analytics", "description": "N/A", "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/module-backend": "100.3.*", - "magento/module-config": "100.3.*", - "magento/module-integration": "100.3.*", - "magento/module-store": "100.3.*", - "magento/framework": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/module-backend": "*", + "magento/module-config": "*", + "magento/module-integration": "*", + "magento/module-store": "*", + "magento/framework": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml index 889517e629e04..4e21648d00ce8 100644 --- a/app/code/Magento/Analytics/etc/adminhtml/system.xml +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -17,14 +17,14 @@ Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the "Go to Advanced Reporting" link. </br> For more information, see our <a href="https://magento.com/legal/terms/cloud-terms"> terms and conditions</a>.]]></comment> - <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Advanced Reporting Service</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> <backend_model>Magento\Analytics\Model\Config\Backend\Enabled</backend_model> <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel</frontend_model> <config_path>analytics/subscription/enabled</config_path> </field> - <field id="collection_time" translate="label comment" type="time" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="collection_time" translate="label" type="time" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Time of day to send data</label> <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel</frontend_model> <backend_model>Magento\Analytics\Model\Config\Backend\CollectionTime</backend_model> diff --git a/app/code/Magento/Analytics/etc/module.xml b/app/code/Magento/Analytics/etc/module.xml index 32ee5d23a4d86..24c2fbc81446e 100644 --- a/app/code/Magento/Analytics/etc/module.xml +++ b/app/code/Magento/Analytics/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Analytics" setup_version="2.0.0"> + <module name="Magento_Analytics" > <sequence> <module name="Magento_Integration"/> <module name="Magento_Backend"/> diff --git a/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php new file mode 100644 index 0000000000000..76410794900e2 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api; + +/** + * Interface BulkStatusInterface. + * + * Bulk summary data with list of operations items short data. + * + * @api + */ +interface BulkStatusInterface extends \Magento\Framework\Bulk\BulkStatusInterface +{ + /** + * Get Bulk summary data with list of operations items full data. + * + * @param string $bulkUuid + * @return \Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getBulkDetailedStatus($bulkUuid); + + /** + * Get Bulk summary data with list of operations items short data. + * + * @param string $bulkUuid + * @return \Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getBulkShortStatus($bulkUuid); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/AsyncResponseInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/AsyncResponseInterface.php new file mode 100644 index 0000000000000..c7edd5c8ff9cd --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/AsyncResponseInterface.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Interface AsyncResponseInterface + * Temporary data object to give response from webapi async router + * + * @api + */ +interface AsyncResponseInterface +{ + const BULK_UUID = 'bulk_uuid'; + const REQUEST_ITEMS = 'request_items'; + const ERRORS = 'errors'; + + /** + * Gets the bulk uuid. + * + * @return string Bulk Uuid. + */ + public function getBulkUuid(); + + /** + * Sets the bulk uuid. + * + * @param string $bulkUuid + * @return $this + */ + public function setBulkUuid($bulkUuid); + + /** + * Gets the list of request items with status data. + * + * @return \Magento\AsynchronousOperations\Api\Data\ItemStatusInterface[] + */ + public function getRequestItems(); + + /** + * Sets the list of request items with status data. + * + * @param \Magento\AsynchronousOperations\Api\Data\ItemStatusInterface[] $requestItems + * @return $this + */ + public function setRequestItems($requestItems); + + /** + * @param bool $isErrors + * @return $this + */ + public function setErrors($isErrors = false); + + /** + * Is there errors during processing bulk + * + * @return boolean + */ + public function isErrors(); + + /** + * Retrieve existing extension attributes object. + * + * @return \Magento\AsynchronousOperations\Api\Data\AsyncResponseExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\AsynchronousOperations\Api\Data\AsyncResponseExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\AsyncResponseExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/BulkOperationsStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/BulkOperationsStatusInterface.php new file mode 100644 index 0000000000000..f8b7e389d387d --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/BulkOperationsStatusInterface.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Interface BulkStatusInterface + * + * Bulk summary data with list of operations items summary data. + * + * @api + */ +interface BulkOperationsStatusInterface extends BulkSummaryInterface +{ + + const OPERATIONS_LIST = 'operations_list'; + + /** + * Retrieve list of operation with statuses (short data). + * + * @return \Magento\AsynchronousOperations\Api\Data\SummaryOperationStatusInterface[] + */ + public function getOperationsList(); + + /** + * Set operations list. + * + * @param \Magento\AsynchronousOperations\Api\Data\SummaryOperationStatusInterface[] $operationStatusList + * @return $this + */ + public function setOperationsList($operationStatusList); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php new file mode 100644 index 0000000000000..a433ec0953a83 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Interface BulkSummaryInterface + * @api + * @since 100.2.0 + */ +interface BulkSummaryInterface extends \Magento\Framework\Bulk\BulkSummaryInterface +{ + const USER_TYPE = 'user_type'; + + /** + * Retrieve existing extension attributes object. + * + * @return \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface|null + * @since 100.2.0 + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface $extensionAttributes + * @return $this + * @since 100.2.0 + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface $extensionAttributes + ); + + /** + * Get user type + * + * @return int + */ + public function getUserType(); + + /** + * Set user type + * + * @param int $userType + * @return $this + */ + public function setUserType($userType); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/DetailedBulkOperationsStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/DetailedBulkOperationsStatusInterface.php new file mode 100644 index 0000000000000..6e39177630857 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/DetailedBulkOperationsStatusInterface.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Interface BulkStatusInterface + * + * Bulk summary data with list of operations items full data. + * + * @api + */ +interface DetailedBulkOperationsStatusInterface extends BulkSummaryInterface +{ + + const OPERATIONS_LIST = 'operations_list'; + + /** + * Retrieve operations list. + * + * @return \Magento\AsynchronousOperations\Api\Data\OperationInterface[] + */ + public function getOperationsList(); + + /** + * Set operations list. + * + * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface[] $operationStatusList + * @return $this + */ + public function setOperationsList($operationStatusList); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/ItemStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/ItemStatusInterface.php new file mode 100644 index 0000000000000..3294078c2c1ea --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/ItemStatusInterface.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * ItemStatusInterface interface + * Temporary object with status of requested item. + * Indicate if entity param was Accepted|Rejected to bulk schedule + * + * @api + */ +interface ItemStatusInterface +{ + const ENTITY_ID = 'entity_id'; + const DATA_HASH = 'data_hash'; + const STATUS = 'status'; + const ERROR_MESSAGE = 'error_message'; + const ERROR_CODE = 'error_code'; + + const STATUS_ACCEPTED = 'accepted'; + const STATUS_REJECTED = 'rejected'; + + /** + * Get entity Id. + * + * @return int + */ + public function getId(); + + /** + * Sets entity Id. + * + * @param int $entityId + * @return $this + */ + public function setId($entityId); + + /** + * Get hash of entity data. + * + * @return string md5 hash of entity params array. + */ + public function getDataHash(); + + /** + * Sets hash of entity data. + * + * @param string $hash md5 hash of entity params array. + * @return $this + */ + public function setDataHash($hash); + + /** + * Get status. + * + * @return string accepted|rejected + */ + public function getStatus(); + + /** + * Sets entity status. + * + * @param string $status accepted|rejected + * @return $this + */ + public function setStatus($status = self::STATUS_ACCEPTED); + + /** + * Get error information. + * + * @return string|null + */ + public function getErrorMessage(); + + /** + * Sets error information. + * + * @param string|null|\Exception $error + * @return $this + */ + public function setErrorMessage($error = null); + + /** + * Get error code. + * + * @return int|null + */ + public function getErrorCode(); + + /** + * Sets error information. + * + * @param int|null|\Exception $errorCode Default: null + * @return $this + */ + public function setErrorCode($errorCode = null); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/OperationInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/OperationInterface.php new file mode 100644 index 0000000000000..95366a96a87b2 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/OperationInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Class OperationInterface + * @api + * @since 100.2.0 + */ +interface OperationInterface extends \Magento\Framework\Bulk\OperationInterface +{ + /** + * Retrieve existing extension attributes object. + * + * @return \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface|null + * @since 100.2.0 + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface $extensionAttributes + * @return $this + * @since 100.2.0 + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/OperationListInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/OperationListInterface.php new file mode 100644 index 0000000000000..474ca6b94885d --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/OperationListInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * List of bulk operations. Used for mass save of operations via entity manager. + * @api + * @since 100.2.0 + */ +interface OperationListInterface +{ + /** + * Get list of operations. + * + * @return \Magento\AsynchronousOperations\Api\Data\OperationInterface[] + * @since 100.2.0 + */ + public function getItems(); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/OperationSearchResultsInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/OperationSearchResultsInterface.php new file mode 100644 index 0000000000000..c3d221b7ef4f8 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/OperationSearchResultsInterface.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Bulk operation search result interface. + * + * An bulk is a group of queue messages. An bulk operation item is a queue message. + * @api + */ +interface OperationSearchResultsInterface extends \Magento\Framework\Api\SearchResultsInterface +{ + /** + * Get list of operations. + * + * @return \Magento\AsynchronousOperations\Api\Data\OperationInterface[] + */ + public function getItems(); + + /** + * Set list of operations. + * + * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface[] $items + * @return $this + */ + public function setItems(array $items); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/SummaryOperationStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/SummaryOperationStatusInterface.php new file mode 100644 index 0000000000000..3b9f53b34162a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/Data/SummaryOperationStatusInterface.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api\Data; + +/** + * Getter Class OperationsStatusInterface + * Instead of OperationInterface this class don't provide all operation data + * and not responsive to set any data, just to get operation data + * without serialized_data and result_serialized_data + * + * @api + */ +interface SummaryOperationStatusInterface +{ + /** + * Operation id + * + * @return int + */ + public function getId(); + + /** + * Get operation status + * + * OPEN | COMPLETE | RETRIABLY_FAILED | NOT_RETRIABLY_FAILED + * + * @return int + */ + public function getStatus(); + + /** + * Get result message + * + * @return string + */ + public function getResultMessage(); + + /** + * Get error code + * + * @return int + */ + public function getErrorCode(); +} diff --git a/app/code/Magento/AsynchronousOperations/Api/OperationRepositoryInterface.php b/app/code/Magento/AsynchronousOperations/Api/OperationRepositoryInterface.php new file mode 100644 index 0000000000000..17547321b827f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/OperationRepositoryInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Api; + +/** + * Bulk operation item repository interface. + * + * An bulk is a group of queue messages. An bulk operation item is a queue message. + * @api + */ +interface OperationRepositoryInterface +{ + /** + * Lists the bulk operation items that match specified search criteria. + * + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * @return \Magento\AsynchronousOperations\Api\Data\OperationSearchResultsInterface + */ + public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria); +} diff --git a/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/BackButton.php b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/BackButton.php new file mode 100644 index 0000000000000..c2591987da012 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/BackButton.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Framework\UrlInterface; + +/** + * Back button configuration provider + */ +class BackButton implements ButtonProviderInterface +{ + /** + * URL builder + * + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @param UrlInterface $urlBuilder + */ + public function __construct( + UrlInterface $urlBuilder + ) { + $this->urlBuilder = $urlBuilder; + } + + /** + * Retrieve button data + * + * @return array button configuration + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->urlBuilder->getUrl('*/')), + 'class' => 'back', + 'sort_order' => 10 + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/DoneButton.php b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/DoneButton.php new file mode 100644 index 0000000000000..5e30c20fd2fbf --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/DoneButton.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Framework\Bulk\OperationInterface; + +/** + * Back button configuration provider + */ +class DoneButton implements ButtonProviderInterface +{ + /** + * @var \Magento\Framework\Bulk\BulkStatusInterface + */ + private $bulkStatus; + + /** + * @var \Magento\Framework\App\RequestInterface + */ + private $request; + + /** + * @param \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus + * @param \Magento\Framework\App\RequestInterface $request + */ + public function __construct( + \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus, + \Magento\Framework\App\RequestInterface $request + ) { + $this->bulkStatus = $bulkStatus; + $this->request = $request; + } + + /** + * Retrieve button data + * + * @return array button configuration + */ + public function getButtonData() + { + $uuid = $this->request->getParam('uuid'); + $operationsCount = $this->bulkStatus->getOperationsCountByBulkIdAndStatus( + $uuid, + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED + ); + $button = []; + + if ($this->request->getParam('buttons') && $operationsCount === 0) { + $button = [ + 'label' => __('Done'), + 'class' => 'primary', + 'sort_order' => 10, + 'on_click' => '', + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'notification_area.notification_area.modalContainer.modal', + 'actionName' => 'closeModal' + ], + ], + ], + ], + ], + ]; + } + + return $button; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/RetryButton.php b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/RetryButton.php new file mode 100644 index 0000000000000..9051f1ab9d428 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Block/Adminhtml/Bulk/Details/RetryButton.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Retry button configuration provider + */ +class RetryButton implements ButtonProviderInterface +{ + /** + * @var \Magento\AsynchronousOperations\Model\Operation\Details + */ + private $details; + + /** + * @var \Magento\Framework\App\RequestInterface + */ + private $request; + + /** + * @var string + */ + private $targetName; + + /** + * RetryButton constructor. + * + * @param \Magento\AsynchronousOperations\Model\Operation\Details $details + * @param \Magento\Framework\App\RequestInterface $request + * @param string $targetName + */ + public function __construct( + \Magento\AsynchronousOperations\Model\Operation\Details $details, + \Magento\Framework\App\RequestInterface $request, + $targetName = 'bulk_details_form.bulk_details_form' + ) { + $this->details = $details; + $this->request = $request; + $this->targetName = $targetName; + } + + /** + * {@inheritdoc} + */ + public function getButtonData() + { + $uuid = $this->request->getParam('uuid'); + $details = $this->details->getDetails($uuid); + if ($details['failed_retriable'] === 0) { + return []; + } + return [ + 'label' => __('Retry'), + 'class' => 'retry primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Details.php b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Details.php new file mode 100644 index 0000000000000..9e9dbd3dd67c5 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Details.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Controller\Adminhtml\Bulk; + +/** + * Class View Opertion Details Controller + */ +class Details extends \Magento\Backend\App\Action +{ + /** + * @var \Magento\Framework\View\Result\PageFactory + */ + private $resultPageFactory; + + /** + * @var \Magento\AsynchronousOperations\Model\AccessValidator + */ + private $accessValidator; + + /** + * @var string + */ + private $menuId; + + /** + * Details constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\AsynchronousOperations\Model\AccessValidator $accessValidator + * @param string $menuId + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\View\Result\PageFactory $resultPageFactory, + \Magento\AsynchronousOperations\Model\AccessValidator $accessValidator, + $menuId = 'Magento_AsynchronousOperations::system_magento_logging_bulk_operations' + ) { + $this->resultPageFactory = $resultPageFactory; + $this->accessValidator = $accessValidator; + $this->menuId = $menuId; + parent::__construct($context); + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Logging::system_magento_logging_bulk_operations') + && $this->accessValidator->isAllowed($this->getRequest()->getParam('uuid')); + } + + /** + * Bulk details action + * + * @return \Magento\Framework\View\Result\Page + */ + public function execute() + { + $bulkId = $this->getRequest()->getParam('uuid'); + $resultPage = $this->resultPageFactory->create(); + $resultPage->initLayout(); + $this->_setActiveMenu($this->menuId); + $resultPage->getConfig()->getTitle()->prepend(__('Action Details - #' . $bulkId)); + + return $resultPage; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Retry.php b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Retry.php new file mode 100644 index 0000000000000..62e6b9ba4551b --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Bulk/Retry.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Controller\Adminhtml\Bulk; + +use Magento\AsynchronousOperations\Model\BulkManagement; +use Magento\AsynchronousOperations\Model\BulkNotificationManagement; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Backend\App\Action; +use Magento\AsynchronousOperations\Model\AccessValidator; +use Magento\Framework\Controller\ResultFactory; + +/** + * Class Bulk Retry Controller + */ +class Retry extends Action +{ + /** + * @var BulkManagement + */ + private $bulkManagement; + + /** + * @var BulkNotificationManagement + */ + private $notificationManagement; + + /** + * @var \Magento\AsynchronousOperations\Model\AccessValidator + */ + private $accessValidator; + + /** + * Retry constructor. + * @param Context $context + * @param BulkManagement $bulkManagement + * @param BulkNotificationManagement $notificationManagement + * @param AccessValidator $accessValidator + */ + public function __construct( + Context $context, + BulkManagement $bulkManagement, + BulkNotificationManagement $notificationManagement, + AccessValidator $accessValidator + ) { + parent::__construct($context); + $this->bulkManagement = $bulkManagement; + $this->notificationManagement = $notificationManagement; + $this->accessValidator = $accessValidator; + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Logging::system_magento_logging_bulk_operations') + && $this->accessValidator->isAllowed($this->getRequest()->getParam('uuid')); + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $bulkUuid = $this->getRequest()->getParam('uuid'); + $isAjax = $this->getRequest()->getParam('isAjax'); + $operationsToRetry = (array)$this->getRequest()->getParam('operations_to_retry', []); + $errorCodes = []; + foreach ($operationsToRetry as $operationData) { + if (isset($operationData['error_code'])) { + $errorCodes[] = (int)$operationData['error_code']; + } + } + + $affectedOperations = $this->bulkManagement->retryBulk($bulkUuid, $errorCodes); + $this->notificationManagement->ignoreBulks([$bulkUuid]); + if (!$isAjax) { + $this->messageManager->addSuccessMessage( + __('%1 item(s) have been scheduled for update."', $affectedOperations) + ); + /** @var Redirect $result */ + $result = $this->resultRedirectFactory->create(); + $result->setPath('bulk/index'); + } else { + /** @var \Magento\Framework\Controller\Result\Json $result */ + $result = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $result->setHttpResponseCode(200); + $response = new \Magento\Framework\DataObject(); + $response->setError(0); + + $result->setData($response); + } + return $result; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Index/Index.php b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Index/Index.php new file mode 100644 index 0000000000000..5a2b9c0a34e64 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Index/Index.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Controller\Adminhtml\Index; + +class Index extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Logging::system_magento_logging_bulk_operations'; + + /** + * @var \Magento\Framework\View\Result\PageFactory + */ + private $resultPageFactory; + + /** + * @var string + */ + private $menuId; + + /** + * Details constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param string $menuId + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\View\Result\PageFactory $resultPageFactory, + $menuId = 'Magento_AsynchronousOperations::system_magento_logging_bulk_operations' + ) { + $this->resultPageFactory = $resultPageFactory; + $this->menuId = $menuId; + parent::__construct($context); + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return parent::_isAllowed(); + } + + /** + * Bulk list action + * + * @return \Magento\Framework\View\Result\Page + */ + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->initLayout(); + $this->_setActiveMenu($this->menuId); + $resultPage->getConfig()->getTitle()->prepend(__('Bulk Actions Log')); + return $resultPage; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php new file mode 100644 index 0000000000000..0a71c130fb20a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Controller\Adminhtml\Notification; + +use Magento\AsynchronousOperations\Model\BulkNotificationManagement; +use Magento\Backend\App\Action\Context; +use Magento\Backend\App\Action; +use Magento\Framework\Controller\ResultFactory; + +/** + * Class Bulk Notification Dismiss Controller + */ +class Dismiss extends Action +{ + /** + * @var BulkNotificationManagement + */ + private $notificationManagement; + + /** + * Class constructor. + * + * @param Context $context + * @param BulkNotificationManagement $notificationManagement + */ + public function __construct( + Context $context, + BulkNotificationManagement $notificationManagement + ) { + parent::__construct($context); + $this->notificationManagement = $notificationManagement; + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Logging::system_magento_logging_bulk_operations'); + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $bulkUuids = []; + foreach ((array)$this->getRequest()->getParam('uuid', []) as $bulkUuid) { + $bulkUuids[] = (string)$bulkUuid; + } + + $isAcknowledged = $this->notificationManagement->acknowledgeBulks($bulkUuids); + + /** @var \Magento\Framework\Controller\Result\Json $result */ + $result = $this->resultFactory->create(ResultFactory::TYPE_JSON); + if (!$isAcknowledged) { + $result->setHttpResponseCode(400); + } + + return $result; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Cron/BulkCleanup.php b/app/code/Magento/AsynchronousOperations/Cron/BulkCleanup.php new file mode 100644 index 0000000000000..7c8da3c1c4236 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Cron/BulkCleanup.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Cron; + +use Magento\Framework\App\ResourceConnection; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\App\Config\ScopeConfigInterface; + +class BulkCleanup +{ + /** + * @var DateTime + */ + private $dateTime; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var \Magento\Framework\Stdlib\DateTime\DateTime + */ + private $date; + + /** + * BulkCleanup constructor. + * @param MetadataPool $metadataPool + * @param ResourceConnection $resourceConnection + * @param DateTime $dateTime + * @param ScopeConfigInterface $scopeConfig + * @param DateTime\DateTime $time + */ + public function __construct( + MetadataPool $metadataPool, + ResourceConnection $resourceConnection, + DateTime $dateTime, + ScopeConfigInterface $scopeConfig, + \Magento\Framework\Stdlib\DateTime\DateTime $time + ) { + $this->metadataPool = $metadataPool; + $this->resourceConnection = $resourceConnection; + $this->dateTime = $dateTime; + $this->scopeConfig = $scopeConfig; + $this->date = $time; + } + + /** + * Remove all expired bulks and corresponding operations + * + * @return void + */ + public function execute() + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + + $bulkLifetime = 3600 * 24 * (int)$this->scopeConfig->getValue('system/bulk/lifetime'); + $maxBulkStartTime = $this->dateTime->formatDate($this->date->gmtTimestamp() - $bulkLifetime); + $connection->delete($metadata->getEntityTable(), ['start_time <= ?' => $maxBulkStartTime]); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/AccessValidator.php b/app/code/Magento/AsynchronousOperations/Model/AccessValidator.php new file mode 100644 index 0000000000000..a14ec254cf897 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/AccessValidator.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +/** + * Class AccessValidator + */ +class AccessValidator +{ + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + + /** + * @var \Magento\Framework\EntityManager\EntityManager + */ + private $entityManager; + + /** + * @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory + */ + private $bulkSummaryFactory; + + /** + * AccessValidator constructor. + * @param \Magento\Authorization\Model\UserContextInterface $userContext + * @param \Magento\Framework\EntityManager\EntityManager $entityManager + * @param \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory $bulkSummaryFactory + */ + public function __construct( + \Magento\Authorization\Model\UserContextInterface $userContext, + \Magento\Framework\EntityManager\EntityManager $entityManager, + \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory $bulkSummaryFactory + ) { + $this->userContext = $userContext; + $this->entityManager = $entityManager; + $this->bulkSummaryFactory = $bulkSummaryFactory; + } + + /** + * Check if content allowed for current user + * + * @param int $bulkUuid + * @return bool + */ + public function isAllowed($bulkUuid) + { + /** @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface $bulkSummary */ + $bulkSummary = $this->entityManager->load( + $this->bulkSummaryFactory->create(), + $bulkUuid + ); + return $bulkSummary->getUserId() === $this->userContext->getUserId(); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/AsyncResponse.php b/app/code/Magento/AsynchronousOperations/Model/AsyncResponse.php new file mode 100644 index 0000000000000..02a2e8de1fa64 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/AsyncResponse.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Api\ExtensibleDataInterface; + +class AsyncResponse extends DataObject implements AsyncResponseInterface, ExtensibleDataInterface +{ + /** + * @inheritDoc + */ + public function getBulkUuid() + { + return $this->getData(self::BULK_UUID); + } + + /** + * @inheritDoc + */ + public function setBulkUuid($bulkUuid) + { + return $this->setData(self::BULK_UUID, $bulkUuid); + } + + /** + * @inheritDoc + */ + public function getRequestItems() + { + return $this->getData(self::REQUEST_ITEMS); + } + + /** + * @inheritDoc + */ + public function setRequestItems($requestItems) + { + return $this->setData(self::REQUEST_ITEMS, $requestItems); + } + + /** + * @inheritdoc + */ + public function setErrors($isErrors = false) + { + return $this->setData(self::ERRORS, $isErrors); + } + + /** + * @inheritdoc + */ + public function isErrors() + { + return $this->getData(self::ERRORS); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes() + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\AsyncResponseExtensionInterface $extensionAttributes + ) { + return $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkDescription/Options.php b/app/code/Magento/AsynchronousOperations/Model/BulkDescription/Options.php new file mode 100644 index 0000000000000..08e1a863b259d --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkDescription/Options.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\BulkDescription; + +use Magento\Framework\Bulk\BulkSummaryInterface; + +/** + * Class for grid options + */ +class Options implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * @var \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory + */ + private $bulkCollectionFactory; + + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + + /** + * Options constructor. + * @param \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory $bulkCollection + * @param \Magento\Authorization\Model\UserContextInterface $userContext + */ + public function __construct( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory $bulkCollection, + \Magento\Authorization\Model\UserContextInterface $userContext + ) { + $this->bulkCollectionFactory = $bulkCollection; + $this->userContext = $userContext; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + /** @var \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection $collection */ + $collection = $this->bulkCollectionFactory->create(); + + /** @var \Magento\Framework\DB\Select $select */ + $select = $collection->getSelect(); + $select->reset(); + $select->distinct(true); + $select->from($collection->getMainTable(), ['description']); + $select->where('user_id = ?', $this->userContext->getUserId()); + + $options = []; + + /** @var BulkSummaryInterface $item */ + foreach ($collection->getItems() as $item) { + $options[] = [ + 'value' => $item->getDescription(), + 'label' => $item->getDescription() + ]; + } + return $options; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php new file mode 100644 index 0000000000000..faf01921e5737 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php @@ -0,0 +1,220 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ResourceConnection; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory; +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\Framework\MessageQueue\BulkPublisherInterface; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; +use Magento\Authorization\Model\UserContextInterface; + +/** + * Class BulkManagement + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface +{ + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var BulkSummaryInterfaceFactory + */ + private $bulkSummaryFactory; + + /** + * @var CollectionFactory + */ + private $operationCollectionFactory; + + /** + * @var BulkPublisherInterface + */ + private $publisher; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * BulkManagement constructor. + * @param EntityManager $entityManager + * @param BulkSummaryInterfaceFactory $bulkSummaryFactory + * @param CollectionFactory $operationCollectionFactory + * @param BulkPublisherInterface $publisher + * @param MetadataPool $metadataPool + * @param ResourceConnection $resourceConnection + * @param \Psr\Log\LoggerInterface $logger + * @param UserContextInterface $userContext + */ + public function __construct( + EntityManager $entityManager, + BulkSummaryInterfaceFactory $bulkSummaryFactory, + CollectionFactory $operationCollectionFactory, + BulkPublisherInterface $publisher, + MetadataPool $metadataPool, + ResourceConnection $resourceConnection, + \Psr\Log\LoggerInterface $logger, + UserContextInterface $userContext = null + ) { + $this->entityManager = $entityManager; + $this->bulkSummaryFactory= $bulkSummaryFactory; + $this->operationCollectionFactory = $operationCollectionFactory; + $this->metadataPool = $metadataPool; + $this->resourceConnection = $resourceConnection; + $this->publisher = $publisher; + $this->logger = $logger; + $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); + } + + /** + * @inheritDoc + */ + public function scheduleBulk($bulkUuid, array $operations, $description, $userId = null) + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + // save bulk summary and related operations + $connection->beginTransaction(); + $userType = $this->userContext->getUserType(); + if ($userType === null) { + $userType = UserContextInterface::USER_TYPE_ADMIN; + } + try { + /** @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface $bulkSummary */ + $bulkSummary = $this->bulkSummaryFactory->create(); + $this->entityManager->load($bulkSummary, $bulkUuid); + $bulkSummary->setBulkId($bulkUuid); + $bulkSummary->setDescription($description); + $bulkSummary->setUserId($userId); + $bulkSummary->setUserType($userType); + $bulkSummary->setOperationCount((int)$bulkSummary->getOperationCount() + count($operations)); + + $this->entityManager->save($bulkSummary); + + $connection->commit(); + } catch (\Exception $exception) { + $connection->rollBack(); + $this->logger->critical($exception->getMessage()); + return false; + } + $this->publishOperations($operations); + + return true; + } + + /** + * Retry bulk operations that failed due to given errors. + * + * @param string $bulkUuid target bulk UUID + * @param array $errorCodes list of corresponding error codes + * @return int number of affected bulk operations + */ + public function retryBulk($bulkUuid, array $errorCodes) + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + + /** @var \Magento\AsynchronousOperations\Model\ResourceModel\Operation[] $retriablyFailedOperations */ + $retriablyFailedOperations = $this->operationCollectionFactory->create() + ->addFieldToFilter('error_code', ['in' => $errorCodes]) + ->addFieldToFilter('bulk_uuid', ['eq' => $bulkUuid]) + ->getItems(); + + // remove corresponding operations from database (i.e. move them to 'open' status) + $connection->beginTransaction(); + try { + $operationIds = []; + $currentBatchSize = 0; + $maxBatchSize = 10000; + /** @var OperationInterface $operation */ + foreach ($retriablyFailedOperations as $operation) { + if ($currentBatchSize === $maxBatchSize) { + $connection->delete( + $this->resourceConnection->getTableName('magento_operation'), + $connection->quoteInto('id IN (?)', $operationIds) + ); + $operationIds = []; + $currentBatchSize = 0; + } + $currentBatchSize++; + $operationIds[] = $operation->getId(); + // Rescheduled operations must be put in queue in 'open' state (i.e. without ID) + $operation->setId(null); + } + // remove operations from the last batch + if (!empty($operationIds)) { + $connection->delete( + $this->resourceConnection->getTableName('magento_operation'), + $connection->quoteInto('id IN (?)', $operationIds) + ); + } + + $connection->commit(); + } catch (\Exception $exception) { + $connection->rollBack(); + $this->logger->critical($exception->getMessage()); + return 0; + } + $this->publishOperations($retriablyFailedOperations); + + return count($retriablyFailedOperations); + } + + /** + * Publish list of operations to the corresponding message queues. + * + * @param array $operations + * @return void + */ + private function publishOperations(array $operations) + { + $operationsByTopics = []; + foreach ($operations as $operation) { + $operationsByTopics[$operation->getTopicName()][] = $operation; + } + foreach ($operationsByTopics as $topicName => $operations) { + $this->publisher->publish($topicName, $operations); + } + } + + /** + * @inheritDoc + */ + public function deleteBulk($bulkId) + { + return $this->entityManager->delete( + $this->entityManager->load( + $this->bulkSummaryFactory->create(), + $bulkId + ) + ); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkNotificationManagement.php b/app/code/Magento/AsynchronousOperations/Model/BulkNotificationManagement.php new file mode 100644 index 0000000000000..2ba7f7fe5e3ee --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkNotificationManagement.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory as BulkCollectionFactory; +use Magento\Framework\Data\Collection; + +/** + * Class for bulk notification manager + */ +class BulkNotificationManagement +{ + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * @var BulkCollectionFactory + */ + private $bulkCollectionFactory; + + /** + * BulkManagement constructor. + * + * @param MetadataPool $metadataPool + * @param ResourceConnection $resourceConnection + * @param BulkCollectionFactory $bulkCollectionFactory + * @param \Psr\Log\LoggerInterface $logger + */ + public function __construct( + MetadataPool $metadataPool, + ResourceConnection $resourceConnection, + BulkCollectionFactory $bulkCollectionFactory, + \Psr\Log\LoggerInterface $logger + ) { + $this->metadataPool = $metadataPool; + $this->resourceConnection = $resourceConnection; + $this->bulkCollectionFactory = $bulkCollectionFactory; + $this->logger = $logger; + } + + /** + * Mark given bulks as acknowledged. + * Notifications related to these bulks will not appear in notification area. + * + * @param array $bulkUuids + * @return bool true on success or false on failure + */ + public function acknowledgeBulks(array $bulkUuids) + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + + try { + $connection->insertArray( + $this->resourceConnection->getTableName('magento_acknowledged_bulk'), + ['bulk_uuid'], + $bulkUuids + ); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + return false; + } + return true; + } + + /** + * Remove given bulks from acknowledged list. + * Notifications related to these bulks will appear again in notification area. + * + * @param array $bulkUuids + * @return bool true on success or false on failure + */ + public function ignoreBulks(array $bulkUuids) + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + + try { + $connection->delete( + $this->resourceConnection->getTableName('magento_acknowledged_bulk'), + ['bulk_uuid IN(?)' => $bulkUuids] + ); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + return false; + } + return true; + } + + /** + * Retrieve all bulks that were acknowledged by given user. + * + * @param int $userId + * @return BulkSummaryInterface[] + */ + public function getAcknowledgedBulksByUser($userId) + { + $bulks = $this->bulkCollectionFactory->create() + ->join( + ['acknowledged_bulk' => $this->resourceConnection->getTableName('magento_acknowledged_bulk')], + 'main_table.uuid = acknowledged_bulk.bulk_uuid', + [] + )->addFieldToFilter('user_id', $userId) + ->addOrder('start_time', Collection::SORT_ORDER_DESC) + ->getItems(); + + return $bulks; + } + + /** + * Retrieve all bulks that were not acknowledged by given user. + * + * @param int $userId + * @return BulkSummaryInterface[] + */ + public function getIgnoredBulksByUser($userId) + { + /** @var \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection $bulkCollection */ + $bulkCollection = $this->bulkCollectionFactory->create(); + $bulkCollection->getSelect()->joinLeft( + ['acknowledged_bulk' => $this->resourceConnection->getTableName('magento_acknowledged_bulk')], + 'main_table.uuid = acknowledged_bulk.bulk_uuid', + ['acknowledged_bulk.bulk_uuid'] + ); + $bulks = $bulkCollection->addFieldToFilter('user_id', $userId) + ->addFieldToFilter('acknowledged_bulk.bulk_uuid', ['null' => true]) + ->addOrder('start_time', Collection::SORT_ORDER_DESC) + ->getItems(); + + return $bulks; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php b/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php new file mode 100644 index 0000000000000..5fc164ec833d8 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\EntityManager\EntityManager; +use Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterfaceFactory as BulkStatusShortFactory; +use Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterfaceFactory as BulkStatusDetailedFactory; +use Magento\AsynchronousOperations\Api\Data\OperationDetailsInterfaceFactory; +use Magento\AsynchronousOperations\Api\BulkStatusInterface; +use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; + +/** + * Class BulkStatus + */ +class BulkOperationsStatus implements BulkStatusInterface +{ + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var BulkStatusDetailedFactory + */ + private $bulkDetailedFactory; + + /** + * @var BulkStatusShortFactory + */ + private $bulkShortFactory; + + /** + * @var BulkStatus + */ + private $bulkStatus; + + /** + * @var CollectionFactory + */ + private $operationCollectionFactory; + + /** + * Init dependencies. + * + * @param BulkStatus $bulkStatus + * @param CollectionFactory $operationCollection + * @param BulkStatusDetailedFactory $bulkDetailedFactory + * @param BulkStatusShortFactory $bulkShortFactory + * @param \Magento\Framework\EntityManager\EntityManager $entityManager + */ + public function __construct( + BulkStatus $bulkStatus, + CollectionFactory $operationCollection, + BulkStatusDetailedFactory $bulkDetailedFactory, + BulkStatusShortFactory $bulkShortFactory, + EntityManager $entityManager + ) { + $this->operationCollectionFactory = $operationCollection; + $this->bulkStatus = $bulkStatus; + $this->bulkDetailedFactory = $bulkDetailedFactory; + $this->bulkShortFactory = $bulkShortFactory; + $this->entityManager = $entityManager; + } + + /** + * @inheritDoc + */ + public function getFailedOperationsByBulkId($bulkUuid, $failureType = null) + { + return $this->bulkStatus->getFailedOperationsByBulkId($bulkUuid, $failureType); + } + + /** + * @inheritDoc + */ + public function getOperationsCountByBulkIdAndStatus($bulkUuid, $status) + { + return $this->bulkStatus->getOperationsCountByBulkIdAndStatus($bulkUuid, $status); + } + + /** + * @inheritDoc + */ + public function getBulksByUser($userId) + { + return $this->bulkStatus->getBulksByUser($userId); + } + + /** + * @inheritDoc + */ + public function getBulkStatus($bulkUuid) + { + return $this->bulkStatus->getBulkStatus($bulkUuid); + } + + /** + * @inheritDoc + */ + public function getBulkDetailedStatus($bulkUuid) + { + $bulkSummary = $this->bulkDetailedFactory->create(); + + /** @var \Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterface $bulk */ + $bulk = $this->entityManager->load($bulkSummary, $bulkUuid); + + if ($bulk->getBulkId() === null) { + throw new NoSuchEntityException( + __( + 'Bulk uuid %bulkUuid not exist', + ['bulkUuid' => $bulkUuid] + ) + ); + } + $operations = $this->operationCollectionFactory->create()->addFieldToFilter('bulk_uuid', $bulkUuid)->getItems(); + $bulk->setOperationsList($operations); + + return $bulk; + } + + /** + * @inheritDoc + */ + public function getBulkShortStatus($bulkUuid) + { + $bulkSummary = $this->bulkShortFactory->create(); + + /** @var \Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface $bulk */ + $bulk = $this->entityManager->load($bulkSummary, $bulkUuid); + if ($bulk->getBulkId() === null) { + throw new NoSuchEntityException( + __( + 'Bulk uuid %bulkUuid not exist', + ['bulkUuid' => $bulkUuid] + ) + ); + } + $operations = $this->operationCollectionFactory->create()->addFieldToFilter('bulk_uuid', $bulkUuid)->getItems(); + $bulk->setOperationsList($operations); + + return $bulk; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus.php new file mode 100644 index 0000000000000..c37ae0d23dd25 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus.php @@ -0,0 +1,200 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\AsynchronousOperations\Model\BulkStatus\CalculatedStatusSql; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Class BulkStatus + */ +class BulkStatus implements \Magento\Framework\Bulk\BulkStatusInterface +{ + /** + * @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory + */ + private $bulkCollectionFactory; + + /** + * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory + */ + private $operationCollectionFactory; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var CalculatedStatusSql + */ + private $calculatedStatusSql; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * BulkStatus constructor. + * @param ResourceModel\Bulk\CollectionFactory $bulkCollection + * @param ResourceModel\Operation\CollectionFactory $operationCollection + * @param ResourceConnection $resourceConnection + * @param CalculatedStatusSql $calculatedStatusSql + * @param MetadataPool $metadataPool + */ + public function __construct( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory $bulkCollection, + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory $operationCollection, + ResourceConnection $resourceConnection, + CalculatedStatusSql $calculatedStatusSql, + MetadataPool $metadataPool + ) { + $this->operationCollectionFactory = $operationCollection; + $this->bulkCollectionFactory = $bulkCollection; + $this->resourceConnection = $resourceConnection; + $this->calculatedStatusSql = $calculatedStatusSql; + $this->metadataPool = $metadataPool; + } + + /** + * @inheritDoc + */ + public function getFailedOperationsByBulkId($bulkUuid, $failureType = null) + { + $failureCodes = $failureType + ? [$failureType] + : [ + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED + ]; + $operations = $this->operationCollectionFactory->create() + ->addFieldToFilter('bulk_uuid', $bulkUuid) + ->addFieldToFilter('status', $failureCodes) + ->getItems(); + return $operations; + } + + /** + * @inheritDoc + */ + public function getOperationsCountByBulkIdAndStatus($bulkUuid, $status) + { + if ($status === OperationInterface::STATUS_TYPE_OPEN) { + /** + * Total number of operations that has been scheduled within the given bulk + */ + $allOperationsQty = $this->getOperationCount($bulkUuid); + + /** + * Number of operations that has been processed (i.e. operations with any status but 'open') + */ + $allProcessedOperationsQty = (int)$this->operationCollectionFactory->create() + ->addFieldToFilter('bulk_uuid', $bulkUuid) + ->getSize(); + + return $allOperationsQty - $allProcessedOperationsQty; + } + + /** @var \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection $collection */ + $collection = $this->operationCollectionFactory->create(); + return $collection->addFieldToFilter('bulk_uuid', $bulkUuid) + ->addFieldToFilter('status', $status) + ->getSize(); + } + + /** + * @inheritDoc + */ + public function getBulksByUser($userId) + { + /** @var ResourceModel\Bulk\Collection $collection */ + $collection = $this->bulkCollectionFactory->create(); + $operationTableName = $this->resourceConnection->getTableName('magento_operation'); + $statusesArray = [ + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, + BulkSummaryInterface::NOT_STARTED, + OperationInterface::STATUS_TYPE_OPEN, + OperationInterface::STATUS_TYPE_COMPLETE + ]; + $select = $collection->getSelect(); + $select->columns(['status' => $this->calculatedStatusSql->get($operationTableName)]) + ->order(new \Zend_Db_Expr('FIELD(status, ' . implode(',', $statusesArray) . ')')); + $collection->addFieldToFilter('user_id', $userId) + ->addOrder('start_time'); + + return $collection->getItems(); + } + + /** + * @inheritDoc + */ + public function getBulkStatus($bulkUuid) + { + /** + * Number of operations that has been processed (i.e. operations with any status but 'open') + */ + $allProcessedOperationsQty = (int)$this->operationCollectionFactory->create() + ->addFieldToFilter('bulk_uuid', $bulkUuid) + ->getSize(); + + if ($allProcessedOperationsQty == 0) { + return BulkSummaryInterface::NOT_STARTED; + } + + /** + * Total number of operations that has been scheduled within the given bulk + */ + $allOperationsQty = $this->getOperationCount($bulkUuid); + + /** + * Number of operations that has not been started yet (i.e. operations with status 'open') + */ + $allOpenOperationsQty = $allOperationsQty - $allProcessedOperationsQty; + + /** + * Number of operations that has been completed successfully + */ + $allCompleteOperationsQty = $this->operationCollectionFactory->create() + ->addFieldToFilter('bulk_uuid', $bulkUuid)->addFieldToFilter( + 'status', + OperationInterface::STATUS_TYPE_COMPLETE + )->getSize(); + + if ($allCompleteOperationsQty == $allOperationsQty) { + return BulkSummaryInterface::FINISHED_SUCCESSFULLY; + } + + if ($allOpenOperationsQty > 0 && $allOpenOperationsQty !== $allOperationsQty) { + return BulkSummaryInterface::IN_PROGRESS; + } + + return BulkSummaryInterface::FINISHED_WITH_FAILURE; + } + + /** + * Get total number of operations that has been scheduled within the given bulk. + * + * @param string $bulkUuid + * @return int + */ + private function getOperationCount($bulkUuid) + { + $metadata = $this->metadataPool->getMetadata(BulkSummaryInterface::class); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + + return (int)$connection->fetchOne( + $connection->select() + ->from($metadata->getEntityTable(), 'operation_count') + ->where('uuid = ?', $bulkUuid) + ); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php new file mode 100644 index 0000000000000..7bdf8a5b7d400 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model\BulkStatus; + +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; + +class CalculatedStatusSql +{ + /** + * Get sql to calculate bulk status + * + * @param string $operationTableName + * @return \Zend_Db_Expr + */ + public function get($operationTableName) + { + return new \Zend_Db_Expr( + '(IF( + (SELECT count(*) + FROM ' . $operationTableName . ' + WHERE bulk_uuid = main_table.uuid + ) = 0, + ' . BulkSummaryInterface::NOT_STARTED . ', + (SELECT MAX(status) FROM ' . $operationTableName . ' WHERE bulk_uuid = main_table.uuid) + ))' + ); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php new file mode 100644 index 0000000000000..334abb33fd106 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model\BulkStatus; + +use Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterface; +use Magento\AsynchronousOperations\Model\BulkSummary; + +class Detailed extends BulkSummary implements DetailedBulkOperationsStatusInterface +{ + /** + * @inheritDoc + */ + public function getOperationsList() + { + return $this->getData(self::OPERATIONS_LIST); + } + + /** + * @inheritDoc + */ + public function setOperationsList($operationStatusList) + { + return $this->setData(self::OPERATIONS_LIST, $operationStatusList); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Options.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Options.php new file mode 100644 index 0000000000000..47c317138ec64 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Options.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\BulkStatus; + +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; + +/** + * Class Options + */ +class Options implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * @return array + */ + public function toOptionArray() + { + return [ + [ + 'value' => BulkSummaryInterface::NOT_STARTED, + 'label' => 'Not Started' + ], + [ + 'value' => BulkSummaryInterface::IN_PROGRESS, + 'label' => 'In Progress' + ], + [ + 'value' => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + 'label' => 'Finished Successfully' + ], + [ + 'value' => BulkSummaryInterface::FINISHED_WITH_FAILURE, + 'label' => 'Finished with Failure' + ] + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php new file mode 100644 index 0000000000000..c6aa99e67202b --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model\BulkStatus; + +use Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface; +use Magento\AsynchronousOperations\Model\BulkSummary; + +class Short extends BulkSummary implements BulkOperationsStatusInterface +{ + /** + * @inheritDoc + */ + public function getOperationsList() + { + return $this->getData(self::OPERATIONS_LIST); + } + + /** + * @inheritDoc + */ + public function setOperationsList($operationStatusList) + { + return $this->setData(self::OPERATIONS_LIST, $operationStatusList); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php b/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php new file mode 100644 index 0000000000000..1d834ad10b2e7 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; +use Magento\Framework\DataObject; + +/** + * Class BulkSummary + */ +class BulkSummary extends DataObject implements BulkSummaryInterface, \Magento\Framework\Api\ExtensibleDataInterface +{ + /** + * @inheritDoc + */ + public function getBulkId() + { + return $this->getData(self::BULK_ID); + } + + /** + * @inheritDoc + */ + public function setBulkId($bulkUuid) + { + return $this->setData(self::BULK_ID, $bulkUuid); + } + + /** + * @inheritDoc + */ + public function getDescription() + { + return $this->getData(self::DESCRIPTION); + } + + /** + * @inheritDoc + */ + public function setDescription($description) + { + return $this->setData(self::DESCRIPTION, $description); + } + + /** + * @inheritDoc + */ + public function getStartTime() + { + return $this->getData(self::START_TIME); + } + + /** + * @inheritDoc + */ + public function setStartTime($timestamp) + { + return $this->setData(self::START_TIME, $timestamp); + } + + /** + * @inheritDoc + */ + public function getUserId() + { + return $this->getData(self::USER_ID); + } + + /** + * @inheritDoc + */ + public function setUserId($userId) + { + return $this->setData(self::USER_ID, $userId); + } + + /** + * @inheritDoc + */ + public function getUserType() + { + return $this->getData(self::USER_TYPE); + } + + /** + * @inheritDoc + */ + public function setUserType($userType) + { + return $this->setData(self::USER_TYPE, $userType); + } + + /** + * @inheritDoc + */ + public function getOperationCount() + { + return $this->getData(self::OPERATION_COUNT); + } + + /** + * @inheritDoc + */ + public function setOperationCount($operationCount) + { + return $this->setData(self::OPERATION_COUNT, $operationCount); + } + + /** + * Retrieve existing extension attributes object. + * + * @return \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object. + * + * @param \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface $extensionAttributes + ) { + return $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php b/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php new file mode 100644 index 0000000000000..de0f89a71650a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\Communication\ConfigInterface as CommunicationConfig; +use Magento\AsynchronousOperations\Api\Data\OperationInterface; + +/** + * Class for accessing to Webapi_Async configuration. + * + * @api + */ +interface ConfigInterface +{ + /**#@+ + * Constants for Webapi Asynchronous Config generation + */ + const CACHE_ID = 'webapi_async_config'; + const TOPIC_PREFIX = 'async.'; + const DEFAULT_CONSUMER_INSTANCE = MassConsumer::class; + const DEFAULT_CONSUMER_CONNECTION = 'amqp'; + const DEFAULT_CONSUMER_MAX_MESSAGE = null; + const SERVICE_PARAM_KEY_INTERFACE = 'interface'; + const SERVICE_PARAM_KEY_METHOD = 'method'; + const SERVICE_PARAM_KEY_TOPIC = 'topic'; + const DEFAULT_HANDLER_NAME = 'async'; + const SYSTEM_TOPIC_NAME = 'async.system.required.wrapper.topic'; + const SYSTEM_TOPIC_CONFIGURATION = [ + CommunicationConfig::TOPIC_NAME => self::SYSTEM_TOPIC_NAME, + CommunicationConfig::TOPIC_IS_SYNCHRONOUS => false, + CommunicationConfig::TOPIC_REQUEST => OperationInterface::class, + CommunicationConfig::TOPIC_REQUEST_TYPE => CommunicationConfig::TOPIC_REQUEST_TYPE_CLASS, + CommunicationConfig::TOPIC_RESPONSE => null, + CommunicationConfig::TOPIC_HANDLERS => [], + ]; + /**#@-*/ + + /** + * Get array of generated topics name and related to this topic service class and methods + * + * @return array + */ + public function getServices(); + + /** + * Get topic name from webapi_async_config services config array by route url and http method + * + * @param string $routeUrl + * @param string $httpMethod GET|POST|PUT|DELETE + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getTopicName($routeUrl, $httpMethod); +} diff --git a/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php b/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php new file mode 100644 index 0000000000000..4abbde4c3602b --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\Entity; + +use Magento\Framework\EntityManager\MapperInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * @deprecated 100.2.0 + */ +class BulkSummaryMapper implements MapperInterface +{ + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param MetadataPool $metadataPool + * @param ResourceConnection $resourceConnection + */ + public function __construct( + MetadataPool $metadataPool, + ResourceConnection $resourceConnection + ) { + $this->metadataPool = $metadataPool; + $this->resourceConnection = $resourceConnection; + } + + /** + * {@inheritdoc} + */ + public function entityToDatabase($entityType, $data) + { + // workaround for delete/update operations that are currently using only primary key as identifier + if (!empty($data['uuid'])) { + $metadata = $this->metadataPool->getMetadata($entityType); + $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); + $select = $connection->select()->from($metadata->getEntityTable(), 'id')->where("uuid = ?", $data['uuid']); + $identifier = $connection->fetchOne($select); + if ($identifier !== false) { + $data['id'] = $identifier; + } + } + return $data; + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function databaseToEntity($entityType, $data) + { + return $data; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php b/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php new file mode 100644 index 0000000000000..b493e0bb663d3 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\ItemStatusInterface; +use Magento\Framework\DataObject; + +class ItemStatus extends DataObject implements ItemStatusInterface +{ + /** + * @inheritDoc + */ + public function getId() + { + return $this->getData(self::ENTITY_ID); + } + + /** + * @inheritDoc + */ + public function setId($entityId) + { + return $this->setData(self::ENTITY_ID, $entityId); + } + + /** + * @inheritDoc + */ + public function getDataHash() + { + return $this->getData(self::DATA_HASH); + } + + /** + * @inheritDoc + */ + public function setDataHash($hash) + { + return $this->setData(self::DATA_HASH, $hash); + } + + /** + * @inheritDoc + */ + public function getStatus() + { + return $this->getData(self::STATUS); + } + + /** + * @inheritDoc + */ + public function setStatus($status = self::STATUS_ACCEPTED) + { + return $this->setData(self::STATUS, $status); + } + + /** + * @inheritDoc + */ + public function getErrorMessage() + { + return $this->getData(self::ERROR_MESSAGE); + } + + /** + * @inheritDoc + */ + public function setErrorMessage($errorMessage = null) + { + if ($errorMessage instanceof \Exception) { + $errorMessage = $errorMessage->getMessage(); + } + + return $this->setData(self::ERROR_MESSAGE, $errorMessage); + } + + /** + * @inheritDoc + */ + public function getErrorCode() + { + return $this->getData(self::ERROR_CODE); + } + + /** + * @inheritDoc + */ + public function setErrorCode($errorCode = null) + { + if ($errorCode instanceof \Exception) { + $errorCode = $errorCode->getCode(); + } + + return $this->setData(self::ERROR_CODE, (int) $errorCode); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php new file mode 100644 index 0000000000000..28bc8141a8e99 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\App\ResourceConnection; +use Psr\Log\LoggerInterface; +use Magento\Framework\MessageQueue\MessageLockException; +use Magento\Framework\MessageQueue\ConnectionLostException; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\MessageQueue\CallbackInvoker; +use Magento\Framework\MessageQueue\ConsumerConfigurationInterface; +use Magento\Framework\MessageQueue\EnvelopeInterface; +use Magento\Framework\MessageQueue\QueueInterface; +use Magento\Framework\MessageQueue\LockInterface; +use Magento\Framework\MessageQueue\MessageController; +use Magento\Framework\MessageQueue\ConsumerInterface; + +/** + * Class Consumer used to process OperationInterface messages. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MassConsumer implements ConsumerInterface +{ + /** + * @var \Magento\Framework\MessageQueue\CallbackInvoker + */ + private $invoker; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var \Magento\Framework\MessageQueue\ConsumerConfigurationInterface + */ + private $configuration; + + /** + * @var \Magento\Framework\MessageQueue\MessageController + */ + private $messageController; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var OperationProcessor + */ + private $operationProcessor; + + /** + * Initialize dependencies. + * + * @param CallbackInvoker $invoker + * @param ResourceConnection $resource + * @param MessageController $messageController + * @param ConsumerConfigurationInterface $configuration + * @param OperationProcessorFactory $operationProcessorFactory + * @param LoggerInterface $logger + */ + public function __construct( + CallbackInvoker $invoker, + ResourceConnection $resource, + MessageController $messageController, + ConsumerConfigurationInterface $configuration, + OperationProcessorFactory $operationProcessorFactory, + LoggerInterface $logger + ) { + $this->invoker = $invoker; + $this->resource = $resource; + $this->messageController = $messageController; + $this->configuration = $configuration; + $this->operationProcessor = $operationProcessorFactory->create([ + 'configuration' => $configuration + ]); + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function process($maxNumberOfMessages = null) + { + $queue = $this->configuration->getQueue(); + + if (!isset($maxNumberOfMessages)) { + $queue->subscribe($this->getTransactionCallback($queue)); + } else { + $this->invoker->invoke($queue, $maxNumberOfMessages, $this->getTransactionCallback($queue)); + } + } + + /** + * Get transaction callback. This handles the case of async. + * + * @param QueueInterface $queue + * @return \Closure + */ + private function getTransactionCallback(QueueInterface $queue) + { + return function (EnvelopeInterface $message) use ($queue) { + /** @var LockInterface $lock */ + $lock = null; + try { + $topicName = $message->getProperties()['topic_name']; + $lock = $this->messageController->lock($message, $this->configuration->getConsumerName()); + + $allowedTopics = $this->configuration->getTopicNames(); + if (in_array($topicName, $allowedTopics)) { + $this->operationProcessor->process($message->getBody()); + } else { + $queue->reject($message); + return; + } + $queue->acknowledge($message); + } catch (MessageLockException $exception) { + $queue->acknowledge($message); + } catch (ConnectionLostException $e) { + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } catch (NotFoundException $e) { + $queue->acknowledge($message); + $this->logger->warning($e->getMessage()); + } catch (\Exception $e) { + $queue->reject($message, false, $e->getMessage()); + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } + }; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php b/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php new file mode 100644 index 0000000000000..5f0f8e28f9fe6 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\MessageQueue\MessageValidator; +use Magento\Framework\MessageQueue\MessageEncoder; +use Magento\Framework\MessageQueue\Publisher\ConfigInterface as PublisherConfig; +use Magento\Framework\MessageQueue\Bulk\ExchangeRepository; +use Magento\Framework\MessageQueue\EnvelopeFactory; +use Magento\AsynchronousOperations\Model\ConfigInterface as AsyncConfig; +use Magento\Framework\MessageQueue\PublisherInterface; +use Magento\Framework\MessageQueue\MessageIdGeneratorInterface; + +/** + * Class MassPublisher used for encoding topic entities to OperationInterface and publish them. + */ +class MassPublisher implements PublisherInterface +{ + /** + * @var \Magento\Framework\MessageQueue\Bulk\ExchangeRepository + */ + private $exchangeRepository; + + /** + * @var \Magento\Framework\MessageQueue\EnvelopeFactory + */ + private $envelopeFactory; + + /** + * @var \Magento\Framework\MessageQueue\MessageEncoder + */ + private $messageEncoder; + + /** + * @var \Magento\Framework\MessageQueue\MessageValidator + */ + private $messageValidator; + + /** + * @var \Magento\Framework\MessageQueue\Publisher\ConfigInterface + */ + private $publisherConfig; + + /** + * @var \Magento\Framework\MessageQueue\MessageIdGeneratorInterface + */ + private $messageIdGenerator; + + /** + * Initialize dependencies. + * + * @param \Magento\Framework\MessageQueue\Bulk\ExchangeRepository $exchangeRepository + * @param \Magento\Framework\MessageQueue\EnvelopeFactory $envelopeFactory + * @param \Magento\Framework\MessageQueue\MessageEncoder $messageEncoder + * @param \Magento\Framework\MessageQueue\MessageValidator $messageValidator + * @param \Magento\Framework\MessageQueue\Publisher\ConfigInterface $publisherConfig + * @param \Magento\Framework\MessageQueue\MessageIdGeneratorInterface $messageIdGenerator + */ + public function __construct( + ExchangeRepository $exchangeRepository, + EnvelopeFactory $envelopeFactory, + MessageEncoder $messageEncoder, + MessageValidator $messageValidator, + PublisherConfig $publisherConfig, + MessageIdGeneratorInterface $messageIdGenerator + ) { + $this->exchangeRepository = $exchangeRepository; + $this->envelopeFactory = $envelopeFactory; + $this->messageEncoder = $messageEncoder; + $this->messageValidator = $messageValidator; + $this->publisherConfig = $publisherConfig; + $this->messageIdGenerator = $messageIdGenerator; + } + + /** + * {@inheritdoc} + */ + public function publish($topicName, $data) + { + $envelopes = []; + foreach ($data as $message) { + $this->messageValidator->validate(AsyncConfig::SYSTEM_TOPIC_NAME, $message); + $message = $this->messageEncoder->encode(AsyncConfig::SYSTEM_TOPIC_NAME, $message); + $envelopes[] = $this->envelopeFactory->create( + [ + 'body' => $message, + 'properties' => [ + 'delivery_mode' => 2, + 'message_id' => $this->messageIdGenerator->generate($topicName), + ] + ] + ); + } + $publisher = $this->publisherConfig->getPublisher($topicName); + $connectionName = $publisher->getConnection()->getName(); + $exchange = $this->exchangeRepository->getByConnectionName($connectionName); + $exchange->enqueue($topicName, $envelopes); + return null; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php new file mode 100644 index 0000000000000..eae92e1663fc8 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -0,0 +1,171 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject\IdentityGeneratorInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; +use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterface; +use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterfaceFactory; +use Magento\AsynchronousOperations\Api\Data\ItemStatusInterface; +use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\Exception\BulkException; +use Psr\Log\LoggerInterface; +use Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository; +use Magento\Authorization\Model\UserContextInterface; + +/** + * Class MassSchedule used for adding multiple entities as Operations to Bulk Management with the status tracking + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Suppressed without refactoring to not introduce BiC + */ +class MassSchedule +{ + /** + * @var \Magento\Framework\DataObject\IdentityGeneratorInterface + */ + private $identityService; + + /** + * @var AsyncResponseInterfaceFactory + */ + private $asyncResponseFactory; + + /** + * @var ItemStatusInterfaceFactory + */ + private $itemStatusInterfaceFactory; + + /** + * @var \Magento\Framework\Bulk\BulkManagementInterface + */ + private $bulkManagement; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var OperationRepository + */ + private $operationRepository; + + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + + /** + * Initialize dependencies. + * + * @param IdentityGeneratorInterface $identityService + * @param ItemStatusInterfaceFactory $itemStatusInterfaceFactory + * @param AsyncResponseInterfaceFactory $asyncResponseFactory + * @param BulkManagementInterface $bulkManagement + * @param LoggerInterface $logger + * @param OperationRepository $operationRepository + * @param UserContextInterface $userContext + */ + public function __construct( + IdentityGeneratorInterface $identityService, + ItemStatusInterfaceFactory $itemStatusInterfaceFactory, + AsyncResponseInterfaceFactory $asyncResponseFactory, + BulkManagementInterface $bulkManagement, + LoggerInterface $logger, + OperationRepository $operationRepository, + UserContextInterface $userContext = null + ) { + $this->identityService = $identityService; + $this->itemStatusInterfaceFactory = $itemStatusInterfaceFactory; + $this->asyncResponseFactory = $asyncResponseFactory; + $this->bulkManagement = $bulkManagement; + $this->logger = $logger; + $this->operationRepository = $operationRepository; + $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); + } + + /** + * Schedule new bulk operation based on the list of entities + * + * @param string $topicName + * @param array $entitiesArray + * @param string $groupId + * @param string $userId + * @return AsyncResponseInterface + * @throws BulkException + * @throws LocalizedException + */ + public function publishMass($topicName, array $entitiesArray, $groupId = null, $userId = null) + { + $bulkDescription = __('Topic %1', $topicName); + + if ($userId == null) { + $userId = $this->userContext->getUserId(); + } + + if ($groupId == null) { + $groupId = $this->identityService->generateId(); + + /** create new bulk without operations */ + if (!$this->bulkManagement->scheduleBulk($groupId, [], $bulkDescription, $userId)) { + throw new LocalizedException( + __('Something went wrong while processing the request.') + ); + } + } + + $operations = []; + $requestItems = []; + $bulkException = new BulkException(); + foreach ($entitiesArray as $key => $entityParams) { + /** @var \Magento\AsynchronousOperations\Api\Data\ItemStatusInterface $requestItem */ + $requestItem = $this->itemStatusInterfaceFactory->create(); + + try { + $operations[] = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId); + $requestItem->setId($key); + $requestItem->setStatus(ItemStatusInterface::STATUS_ACCEPTED); + $requestItems[] = $requestItem; + } catch (\Exception $exception) { + $this->logger->error($exception); + $requestItem->setId($key); + $requestItem->setStatus(ItemStatusInterface::STATUS_REJECTED); + $requestItem->setErrorMessage($exception); + $requestItem->setErrorCode($exception); + $requestItems[] = $requestItem; + $bulkException->addException(new LocalizedException( + __('Error processing %key element of input data', ['key' => $key]), + $exception + )); + } + } + + if (!$this->bulkManagement->scheduleBulk($groupId, $operations, $bulkDescription, $userId)) { + throw new LocalizedException( + __('Something went wrong while processing the request.') + ); + } + /** @var AsyncResponseInterface $asyncResponse */ + $asyncResponse = $this->asyncResponseFactory->create(); + $asyncResponse->setBulkUuid($groupId); + $asyncResponse->setRequestItems($requestItems); + + if ($bulkException->wasErrorAdded()) { + $asyncResponse->setErrors(true); + $bulkException->addData($asyncResponse); + throw $bulkException; + } else { + $asyncResponse->setErrors(false); + } + + return $asyncResponse; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/Operation.php b/app/code/Magento/AsynchronousOperations/Model/Operation.php new file mode 100644 index 0000000000000..dbe6ecc1b6b1f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/Operation.php @@ -0,0 +1,165 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\Framework\DataObject; + +/** + * Class Operation + */ +class Operation extends DataObject implements OperationInterface +{ + /** + * @inheritDoc + */ + public function getId() + { + return $this->getData(self::ID); + } + + /** + * @inheritDoc + */ + public function setId($id) + { + return $this->setData(self::ID, $id); + } + + /** + * @inheritDoc + */ + public function getBulkUuid() + { + return $this->getData(self::BULK_ID); + } + + /** + * @inheritDoc + */ + public function setBulkUuid($bulkId) + { + return $this->setData(self::BULK_ID, $bulkId); + } + + /** + * @inheritDoc + */ + public function getTopicName() + { + return $this->getData(self::TOPIC_NAME); + } + + /** + * @inheritDoc + */ + public function setTopicName($topic) + { + return $this->setData(self::TOPIC_NAME, $topic); + } + + /** + * @inheritDoc + */ + public function getSerializedData() + { + return $this->getData(self::SERIALIZED_DATA); + } + + /** + * @inheritDoc + */ + public function setSerializedData($serializedData) + { + return $this->setData(self::SERIALIZED_DATA, $serializedData); + } + + /** + * @inheritDoc + */ + public function getResultSerializedData() + { + return $this->getData(self::RESULT_SERIALIZED_DATA); + } + + /** + * @inheritDoc + */ + public function setResultSerializedData($resultSerializedData) + { + return $this->setData(self::RESULT_SERIALIZED_DATA, $resultSerializedData); + } + + /** + * @inheritDoc + */ + public function getStatus() + { + return $this->getData(self::STATUS); + } + + /** + * @inheritDoc + */ + public function setStatus($status) + { + return $this->setData(self::STATUS, $status); + } + + /** + * @inheritDoc + */ + public function getResultMessage() + { + return $this->getData(self::RESULT_MESSAGE); + } + + /** + * @inheritDoc + */ + public function setResultMessage($resultMessage) + { + return $this->setData(self::RESULT_MESSAGE, $resultMessage); + } + + /** + * @inheritDoc + */ + public function getErrorCode() + { + return $this->getData(self::ERROR_CODE); + } + + /** + * @inheritDoc + */ + public function setErrorCode($errorCode) + { + return $this->setData(self::ERROR_CODE, $errorCode); + } + + /** + * Retrieve existing extension attributes object. + * + * @return \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object. + * + * @param \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterface $extensionAttributes + ) { + return $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php b/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php new file mode 100644 index 0000000000000..d248f9c3e9276 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php @@ -0,0 +1,164 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model\Operation; + +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\Bulk\BulkStatusInterface; + +class Details +{ + /** + * @var array + */ + private $operationCache = []; + + /** + * @var \Magento\Framework\Bulk\BulkStatusInterface + */ + private $bulkStatus; + + /** + * @var null + */ + private $bulkUuid; + + /** + * Map between status codes and human readable indexes + * + * @var array + */ + private $statusMap = [ + OperationInterface::STATUS_TYPE_COMPLETE => 'operations_successful', + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED => 'failed_retriable', + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED => 'failed_not_retriable', + OperationInterface::STATUS_TYPE_OPEN => 'open', + OperationInterface::STATUS_TYPE_REJECTED => 'rejected', + ]; + + /** + * Init dependencies. + * + * @param \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus + * @param null $bulkUuid + */ + public function __construct( + BulkStatusInterface $bulkStatus, + $bulkUuid = null + ) { + $this->bulkStatus = $bulkStatus; + $this->bulkUuid = $bulkUuid; + } + + /** + * Collect operations statistics for the bulk + * + * @param string $bulkUuid + * @return array + */ + public function getDetails($bulkUuid) + { + $details = [ + 'operations_total' => 0, + 'operations_successful' => 0, + 'operations_failed' => 0, + 'failed_retriable' => 0, + 'failed_not_retriable' => 0, + 'rejected' => 0, + ]; + + if (array_key_exists($bulkUuid, $this->operationCache)) { + return $this->operationCache[$bulkUuid]; + } + + foreach ($this->statusMap as $statusCode => $readableKey) { + $details[$readableKey] = $this->bulkStatus->getOperationsCountByBulkIdAndStatus( + $bulkUuid, + $statusCode + ); + } + + $details['operations_total'] = array_sum($details); + $details['operations_failed'] = $details['failed_retriable'] + $details['failed_not_retriable']; + $this->operationCache[$bulkUuid] = $details; + + return $details; + } + + /** + * @inheritDoc + */ + public function getOperationsTotal() + { + $this->getDetails($this->bulkUuid); + + return $this->operationCache[$this->bulkUuid]['operations_total']; + } + + /** + * @inheritDoc + */ + public function getOpen() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_OPEN]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getOperationsSuccessful() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_COMPLETE]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getTotalFailed() + { + $this->getDetails($this->bulkUuid); + + return $this->operationCache[$this->bulkUuid]['operations_failed']; + } + + /** + * @inheritDoc + */ + public function getFailedNotRetriable() + { + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getFailedRetriable() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_RETRIABLY_FAILED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getRejected() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_REJECTED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationList.php b/app/code/Magento/AsynchronousOperations/Model/OperationList.php new file mode 100644 index 0000000000000..7de62107415b0 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationList.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +/** + * List of bulk operations. + */ +class OperationList implements \Magento\AsynchronousOperations\Api\Data\OperationListInterface +{ + /** + * @var array + */ + private $items; + + /** + * @param array $items [optional] + */ + public function __construct(array $items = []) + { + $this->items = $items; + } + + /** + * @inheritdoc + */ + public function getItems() + { + return $this->items; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php b/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php new file mode 100644 index 0000000000000..f204f63ed032b --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\Framework\EntityManager\EntityManager; + +/** + * Class OperationManagement + */ +class OperationManagement implements \Magento\Framework\Bulk\OperationManagementInterface +{ + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var OperationInterfaceFactory + */ + private $operationFactory; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * OperationManagement constructor. + * + * @param EntityManager $entityManager + * @param OperationInterfaceFactory $operationFactory + * @param \Psr\Log\LoggerInterface $logger + */ + public function __construct( + EntityManager $entityManager, + OperationInterfaceFactory $operationFactory, + \Psr\Log\LoggerInterface $logger + ) { + $this->entityManager = $entityManager; + $this->operationFactory = $operationFactory; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function changeOperationStatus( + $operationId, + $status, + $errorCode = null, + $message = null, + $data = null, + $resultData = null + ) { + try { + $operationEntity = $this->operationFactory->create(); + $this->entityManager->load($operationEntity, $operationId); + $operationEntity->setErrorCode($errorCode); + $operationEntity->setStatus($status); + $operationEntity->setResultMessage($message); + $operationEntity->setSerializedData($data); + $operationEntity->setResultSerializedData($resultData); + $this->entityManager->save($operationEntity); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + return false; + } + return true; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php b/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php new file mode 100644 index 0000000000000..6826c34fd35f0 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php @@ -0,0 +1,227 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\Serialize\Serializer\Json; +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\Framework\Bulk\OperationManagementInterface; +use Magento\AsynchronousOperations\Model\ConfigInterface as AsyncConfig; +use Magento\Framework\MessageQueue\MessageValidator; +use Magento\Framework\MessageQueue\MessageEncoder; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\MessageQueue\ConsumerConfigurationInterface; +use Psr\Log\LoggerInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\TemporaryStateExceptionInterface; +use Magento\Framework\DB\Adapter\ConnectionException; +use Magento\Framework\DB\Adapter\DeadlockException; +use Magento\Framework\DB\Adapter\LockWaitException; +use Magento\Framework\Webapi\ServiceOutputProcessor; +use Magento\Framework\Communication\ConfigInterface as CommunicationConfig; + +/** + * Class OperationProcessor + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class OperationProcessor +{ + /** + * @var Json + */ + private $jsonHelper; + + /** + * @var OperationManagementInterface + */ + private $operationManagement; + + /** + * @var MessageEncoder + */ + private $messageEncoder; + + /** + * @var MessageValidator + */ + private $messageValidator; + + /** + * @var ConsumerConfigurationInterface + */ + private $configuration; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var ServiceOutputProcessor + */ + private $serviceOutputProcessor; + + /** + * @var CommunicationConfig + */ + private $communicationConfig; + + /** + * OperationProcessor constructor. + * + * @param MessageValidator $messageValidator + * @param MessageEncoder $messageEncoder + * @param ConsumerConfigurationInterface $configuration + * @param Json $jsonHelper + * @param OperationManagementInterface $operationManagement + * @param LoggerInterface $logger + * @param \Magento\Framework\Webapi\ServiceOutputProcessor $serviceOutputProcessor + * @param \Magento\Framework\Communication\ConfigInterface $communicationConfig + */ + public function __construct( + MessageValidator $messageValidator, + MessageEncoder $messageEncoder, + ConsumerConfigurationInterface $configuration, + Json $jsonHelper, + OperationManagementInterface $operationManagement, + ServiceOutputProcessor $serviceOutputProcessor, + CommunicationConfig $communicationConfig, + LoggerInterface $logger + ) { + $this->messageValidator = $messageValidator; + $this->messageEncoder = $messageEncoder; + $this->configuration = $configuration; + $this->jsonHelper = $jsonHelper; + $this->operationManagement = $operationManagement; + $this->logger = $logger; + $this->serviceOutputProcessor = $serviceOutputProcessor; + $this->communicationConfig = $communicationConfig; + } + + /** + * Process topic-based encoded message + * + * @param string $encodedMessage + * @return void + */ + public function process(string $encodedMessage) + { + $operation = $this->messageEncoder->decode(AsyncConfig::SYSTEM_TOPIC_NAME, $encodedMessage); + $this->messageValidator->validate(AsyncConfig::SYSTEM_TOPIC_NAME, $operation); + + $status = OperationInterface::STATUS_TYPE_COMPLETE; + $errorCode = null; + $messages = []; + $topicName = $operation->getTopicName(); + $handlers = $this->configuration->getHandlers($topicName); + try { + $data = $this->jsonHelper->unserialize($operation->getSerializedData()); + $entityParams = $this->messageEncoder->decode($topicName, $data['meta_information']); + $this->messageValidator->validate($topicName, $entityParams); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $errorCode = $e->getCode(); + $messages[] = $e->getMessage(); + } + + $outputData = null; + if ($errorCode === null) { + foreach ($handlers as $callback) { + $result = $this->executeHandler($callback, $entityParams); + $status = $result['status']; + $errorCode = $result['error_code']; + $messages = array_merge($messages, $result['messages']); + $outputData = $result['output_data']; + } + } + + if (isset($outputData)) { + try { + $communicationConfig = $this->communicationConfig->getTopic($topicName); + $asyncHandler = + $communicationConfig[CommunicationConfig::TOPIC_HANDLERS][AsyncConfig::DEFAULT_HANDLER_NAME]; + $serviceClass = $asyncHandler[CommunicationConfig::HANDLER_TYPE]; + $serviceMethod = $asyncHandler[CommunicationConfig::HANDLER_METHOD]; + $outputData = $this->serviceOutputProcessor->process( + $outputData, + $serviceClass, + $serviceMethod + ); + $outputData = $this->jsonHelper->serialize($outputData); + } catch (\Exception $e) { + $messages[] = $e->getMessage(); + } + } + + $serializedData = (isset($errorCode)) ? $operation->getSerializedData() : null; + $this->operationManagement->changeOperationStatus( + $operation->getId(), + $status, + $errorCode, + implode('; ', $messages), + $serializedData, + $outputData + ); + } + + /** + * Execute topic handler + * + * @param $callback + * @param $entityParams + * @return array + */ + private function executeHandler($callback, $entityParams) + { + $result = [ + 'status' => OperationInterface::STATUS_TYPE_COMPLETE, + 'error_code' => null, + 'messages' => [], + 'output_data' => null + ]; + try { + $result['output_data'] = call_user_func_array($callback, $entityParams); + $result['messages'][] = sprintf('Service execution success %s::%s', get_class($callback[0]), $callback[1]); + } catch (\Zend_Db_Adapter_Exception $e) { + $this->logger->critical($e->getMessage()); + if ($e instanceof LockWaitException + || $e instanceof DeadlockException + || $e instanceof ConnectionException + ) { + $result['status'] = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = __($e->getMessage()); + } else { + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = + __('Sorry, something went wrong during product prices update. Please see log for details.'); + } + } catch (NoSuchEntityException $e) { + $this->logger->error($e->getMessage()); + $result['status'] = ($e instanceof TemporaryStateExceptionInterface) ? + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED : + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } catch (LocalizedException $e) { + $this->logger->error($e->getMessage()); + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } + return $result; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/OperationRepository.php new file mode 100644 index 0000000000000..ec76ff6519757 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationRepository.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\AsynchronousOperations\Api\Data\OperationSearchResultsInterfaceFactory as SearchResultFactory; +use Magento\AsynchronousOperations\Api\Data\OperationExtensionInterfaceFactory; +use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Repository class for @see \Magento\AsynchronousOperations\Api\OperationRepositoryInterface + */ +class OperationRepository implements \Magento\AsynchronousOperations\Api\OperationRepositoryInterface +{ + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var SearchResultFactory + */ + private $searchResultFactory; + + /** + * @var JoinProcessorInterface + */ + private $joinProcessor; + + /** + * @var \Magento\AsynchronousOperations\Api\Data\OperationExtensionInterfaceFactory + */ + private $operationExtensionFactory; + + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * OperationRepository constructor. + * + * @param EntityManager $entityManager + * @param CollectionFactory $collectionFactory + * @param SearchResultFactory $searchResultFactory + * @param JoinProcessorInterface $joinProcessor + * @param OperationExtensionInterfaceFactory $operationExtension + * @param CollectionProcessorInterface $collectionProcessor + * @param \Psr\Log\LoggerInterface $logger + */ + public function __construct( + EntityManager $entityManager, + CollectionFactory $collectionFactory, + SearchResultFactory $searchResultFactory, + JoinProcessorInterface $joinProcessor, + OperationExtensionInterfaceFactory $operationExtension, + CollectionProcessorInterface $collectionProcessor, + \Psr\Log\LoggerInterface $logger + ) { + $this->entityManager = $entityManager; + $this->collectionFactory = $collectionFactory; + $this->searchResultFactory = $searchResultFactory; + $this->joinProcessor = $joinProcessor; + $this->operationExtensionFactory = $operationExtension; + $this->collectionProcessor = $collectionProcessor; + $this->logger = $logger; + $this->collectionProcessor = $collectionProcessor; + } + + /** + * @inheritDoc + */ + public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) + { + /** @var \Magento\AsynchronousOperations\Api\Data\OperationSearchResultsInterface $searchResult */ + $searchResult = $this->searchResultFactory->create(); + + /** @var \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection $collection */ + $collection = $this->collectionFactory->create(); + $this->joinProcessor->process($collection, \Magento\AsynchronousOperations\Api\Data\OperationInterface::class); + $this->collectionProcessor->process($searchCriteria, $collection); + $searchResult->setSearchCriteria($searchCriteria); + $searchResult->setTotalCount($collection->getSize()); + $searchResult->setItems($collection->getItems()); + + return $searchResult; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php b/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php new file mode 100644 index 0000000000000..5c975bd1a9a45 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\SummaryOperationStatusInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Class OperationShortDetails + */ +class OperationStatus extends DataObject implements SummaryOperationStatusInterface, ExtensibleDataInterface +{ + /** + * @inheritDoc + */ + public function getId() + { + return $this->getData(OperationInterface::ID); + } + + /** + * @inheritDoc + */ + public function getStatus() + { + return $this->getData(OperationInterface::STATUS); + } + + /** + * @inheritDoc + */ + public function getResultMessage() + { + return $this->getData(OperationInterface::RESULT_MESSAGE); + } + + /** + * @inheritDoc + */ + public function getErrorCode() + { + return $this->getData(OperationInterface::ERROR_CODE); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk.php new file mode 100644 index 0000000000000..d56d54359ee9a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model\ResourceModel; + +/** + * Class Bulk + */ +class Bulk extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * Initialize banner sales rule resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('magento_bulk', 'uuid'); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk/Collection.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk/Collection.php new file mode 100644 index 0000000000000..6dd997c5333ff --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Bulk/Collection.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\ResourceModel\Bulk; + +/** + * Class Collection + * @codeCoverageIgnore + */ +class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +{ + /** + * Define collection item type and corresponding table + * + * @return void + */ + protected function _construct() + { + $this->_init( + \Magento\AsynchronousOperations\Model\BulkSummary::class, + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk::class + ); + $this->setMainTable('magento_bulk'); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation.php new file mode 100644 index 0000000000000..061d0917e7ab0 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model\ResourceModel; + +/** + * Class Operation + */ +class Operation extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * Initialize banner sales rule resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('magento_operation', 'id'); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/CheckIfExists.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/CheckIfExists.php new file mode 100644 index 0000000000000..46c4e4bc1c2bc --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/CheckIfExists.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\ResourceModel\Operation; + +use Magento\Framework\EntityManager\Operation\CheckIfExistsInterface; +use Magento\Framework\App\ResourceConnection; + +/** + * CheckIfExists operation for list of bulk operations. + */ +class CheckIfExists implements CheckIfExistsInterface +{ + /** + * Always returns false because all operations will be saved using insertOnDuplicate query. + * + * @param object $entity + * @param array $arguments + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute($entity, $arguments = []) + { + return false; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php new file mode 100644 index 0000000000000..18c5f09794c77 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php @@ -0,0 +1,28 @@ +<?php + +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\ResourceModel\Operation; + +/** + * Class Collection + * @codeCoverageIgnore + */ +class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +{ + /** + * Define collection item type and corresponding table + * + * @return void + */ + protected function _construct() + { + $this->_init( + \Magento\AsynchronousOperations\Model\Operation::class, + \Magento\AsynchronousOperations\Model\ResourceModel\Operation::class + ); + $this->setMainTable('magento_operation'); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Create.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Create.php new file mode 100644 index 0000000000000..ce2357c6b2b4f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Create.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\ResourceModel\Operation; + +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\EntityManager\TypeResolver; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Phrase; + +/** + * Create operation for list of bulk operations. + */ +class Create implements \Magento\Framework\EntityManager\Operation\CreateInterface +{ + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var TypeResolver + */ + private $typeResolver; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param MetadataPool $metadataPool + * @param TypeResolver $typeResolver + * @param ResourceConnection $resourceConnection + */ + public function __construct( + MetadataPool $metadataPool, + TypeResolver $typeResolver, + ResourceConnection $resourceConnection + ) { + $this->metadataPool = $metadataPool; + $this->typeResolver = $typeResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Save all operations from the list in one query. + * + * @param object $entity + * @param array $arguments + * @return object + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute($entity, $arguments = []) + { + $entityType = $this->typeResolver->resolve($entity); + $metadata = $this->metadataPool->getMetadata($entityType); + $connection = $this->resourceConnection->getConnection($metadata->getEntityConnectionName()); + try { + $connection->beginTransaction(); + $data = []; + foreach ($entity->getItems() as $operation) { + $data[] = $operation->getData(); + } + $connection->insertOnDuplicate( + $metadata->getEntityTable(), + $data, + [ + 'status', + 'error_code', + 'result_message', + ] + ); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw $e; + } + return $entity; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php new file mode 100644 index 0000000000000..54e65cc3470dd --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model\ResourceModel\Operation; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\Framework\MessageQueue\MessageValidator; +use Magento\Framework\MessageQueue\MessageEncoder; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\EntityManager\EntityManager; + +/** + * Create operation for list of bulk operations. + */ +class OperationRepository +{ + /** + * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory + */ + private $operationFactory; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var MessageEncoder + */ + private $messageEncoder; + + /** + * @var MessageValidator + */ + private $messageValidator; + + /** + * @param OperationInterfaceFactory $operationFactory + * @param EntityManager $entityManager + * @param MessageValidator $messageValidator + * @param MessageEncoder $messageEncoder + * @param Json $jsonSerializer + */ + public function __construct( + OperationInterfaceFactory $operationFactory, + EntityManager $entityManager, + MessageValidator $messageValidator, + MessageEncoder $messageEncoder, + Json $jsonSerializer + ) { + $this->operationFactory = $operationFactory; + $this->jsonSerializer = $jsonSerializer; + $this->messageEncoder = $messageEncoder; + $this->messageValidator = $messageValidator; + $this->entityManager = $entityManager; + } + + /** + * @param $topicName + * @param $entityParams + * @param $groupId + * @return mixed + */ + public function createByTopic($topicName, $entityParams, $groupId) + { + $this->messageValidator->validate($topicName, $entityParams); + $encodedMessage = $this->messageEncoder->encode($topicName, $entityParams); + + $serializedData = [ + 'entity_id' => null, + 'entity_link' => '', + 'meta_information' => $encodedMessage, + ]; + $data = [ + 'data' => [ + OperationInterface::BULK_ID => $groupId, + OperationInterface::TOPIC_NAME => $topicName, + OperationInterface::SERIALIZED_DATA => $this->jsonSerializer->serialize($serializedData), + OperationInterface::STATUS => OperationInterface::STATUS_TYPE_OPEN, + ], + ]; + + /** @var \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation */ + $operation = $this->operationFactory->create($data); + return $this->entityManager->save($operation); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php new file mode 100644 index 0000000000000..8457a641ed9a9 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php @@ -0,0 +1,171 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Model\ResourceModel\System\Message\Collection\Synchronized; + +/** + * Class Plugin to add bulks related notification messages to Synchronized Collection + */ +class Plugin +{ + /** + * @var \Magento\AdminNotification\Model\System\MessageFactory + */ + private $messageFactory; + + /** + * @var \Magento\Framework\Bulk\BulkStatusInterface + */ + private $bulkStatus; + + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + + /** + * @var \Magento\AsynchronousOperations\Model\Operation\Details + */ + private $operationDetails; + + /** + * @var \Magento\AsynchronousOperations\Model\BulkNotificationManagement + */ + private $bulkNotificationManagement; + + /** + * @var \Magento\Framework\AuthorizationInterface + */ + private $authorization; + + /** + * @var \Magento\AsynchronousOperations\Model\StatusMapper + */ + private $statusMapper; + + /** + * Plugin constructor. + * + * @param \Magento\AdminNotification\Model\System\MessageFactory $messageFactory + * @param \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus + * @param \Magento\AsynchronousOperations\Model\BulkNotificationManagement $bulkNotificationManagement + * @param \Magento\Authorization\Model\UserContextInterface $userContext + * @param \Magento\AsynchronousOperations\Model\Operation\Details $operationDetails + * @param \Magento\Framework\AuthorizationInterface $authorization + * @param \Magento\AsynchronousOperations\Model\StatusMapper $statusMapper + */ + public function __construct( + \Magento\AdminNotification\Model\System\MessageFactory $messageFactory, + \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus, + \Magento\AsynchronousOperations\Model\BulkNotificationManagement $bulkNotificationManagement, + \Magento\Authorization\Model\UserContextInterface $userContext, + \Magento\AsynchronousOperations\Model\Operation\Details $operationDetails, + \Magento\Framework\AuthorizationInterface $authorization, + \Magento\AsynchronousOperations\Model\StatusMapper $statusMapper + ) { + $this->messageFactory = $messageFactory; + $this->bulkStatus = $bulkStatus; + $this->userContext = $userContext; + $this->operationDetails = $operationDetails; + $this->bulkNotificationManagement = $bulkNotificationManagement; + $this->authorization = $authorization; + $this->statusMapper = $statusMapper; + } + + /** + * Adding bulk related messages to notification area + * + * @param \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $collection + * @param array $result + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterToArray( + \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $collection, + $result + ) { + if (!$this->authorization->isAllowed('Magento_Logging::system_magento_logging_bulk_operations')) { + return $result; + } + $userId = $this->userContext->getUserId(); + $userBulks = $this->bulkStatus->getBulksByUser($userId); + $acknowledgedBulks = $this->getAcknowledgedBulksUuid( + $this->bulkNotificationManagement->getAcknowledgedBulksByUser($userId) + ); + $bulkMessages = []; + foreach ($userBulks as $bulk) { + $bulkUuid = $bulk->getBulkId(); + if (!in_array($bulkUuid, $acknowledgedBulks)) { + $details = $this->operationDetails->getDetails($bulkUuid); + $text = $this->getText($details); + $bulkStatus = $this->statusMapper->operationStatusToBulkSummaryStatus($bulk->getStatus()); + if ($bulkStatus === \Magento\Framework\Bulk\BulkSummaryInterface::IN_PROGRESS) { + $text = __('%1 item(s) are currently being updated.', $details['operations_total']) . $text; + } + $data = [ + 'data' => [ + 'text' => __('Task "%1": ', $bulk->getDescription()) . $text, + 'severity' => \Magento\Framework\Notification\MessageInterface::SEVERITY_MAJOR, + 'identity' => md5('bulk' . $bulkUuid), + 'uuid' => $bulkUuid, + 'status' => $bulkStatus, + 'created_at' => $bulk->getStartTime() + ] + ]; + $bulkMessages[] = $this->messageFactory->create($data)->toArray(); + } + } + + if (!empty($bulkMessages)) { + $result['totalRecords'] += count($bulkMessages); + $bulkMessages = array_slice($bulkMessages, 0, 5); + $result['items'] = array_merge($bulkMessages, $result['items']); + } + return $result; + } + + /** + * Get Bulk notification message + * + * @param array $operationDetails + * @return \Magento\Framework\Phrase|string + */ + private function getText($operationDetails) + { + if (0 == $operationDetails['operations_successful'] && 0 == $operationDetails['operations_failed']) { + return __('%1 item(s) have been scheduled for update.', $operationDetails['operations_total']); + } + + $summaryReport = ''; + if ($operationDetails['operations_successful'] > 0) { + $summaryReport .= __( + '%1 item(s) have been successfully updated.', + $operationDetails['operations_successful'] + ); + } + + if ($operationDetails['operations_failed'] > 0) { + $summaryReport .= '<strong>' + . __('%1 item(s) failed to update', $operationDetails['operations_failed']) + . '</strong>'; + } + return $summaryReport; + } + + /** + * Get array with acknowledgedBulksUuid + * + * @param array $acknowledgedBulks + * @return array + */ + private function getAcknowledgedBulksUuid($acknowledgedBulks) + { + $acknowledgedBulksArray = []; + foreach ($acknowledgedBulks as $bulk) { + $acknowledgedBulksArray[] = $bulk->getBulkId(); + } + return $acknowledgedBulksArray; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php b/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php new file mode 100644 index 0000000000000..e5aee6d2f59fa --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\Bulk\BulkSummaryInterface; + +/** + * Class StatusMapper + */ +class StatusMapper +{ + /** + * Map operation status to bulk summary status + * + * @param int $operationStatus + * @return null|int + */ + public function operationStatusToBulkSummaryStatus($operationStatus) + { + $statusMapping = [ + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED => BulkSummaryInterface::FINISHED_WITH_FAILURE, + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED => BulkSummaryInterface::FINISHED_WITH_FAILURE, + OperationInterface::STATUS_TYPE_REJECTED => BulkSummaryInterface::FINISHED_WITH_FAILURE, + OperationInterface::STATUS_TYPE_COMPLETE => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + OperationInterface::STATUS_TYPE_OPEN => BulkSummaryInterface::IN_PROGRESS, + BulkSummaryInterface::NOT_STARTED => BulkSummaryInterface::NOT_STARTED + ]; + + if (isset($statusMapping[$operationStatus])) { + return $statusMapping[$operationStatus]; + } + return null; + } + + /** + * Map bulk summary status to operation status + * + * @param int $bulkStatus + * @return int|null + */ + public function bulkSummaryStatusToOperationStatus($bulkStatus) + { + $statusMapping = [ + BulkSummaryInterface::FINISHED_WITH_FAILURE => [ + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_REJECTED + ], + BulkSummaryInterface::FINISHED_SUCCESSFULLY => OperationInterface::STATUS_TYPE_COMPLETE, + BulkSummaryInterface::IN_PROGRESS => OperationInterface::STATUS_TYPE_OPEN, + BulkSummaryInterface::NOT_STARTED => BulkSummaryInterface::NOT_STARTED + ]; + + if (isset($statusMapping[$bulkStatus])) { + return $statusMapping[$bulkStatus]; + } + return null; + } +} diff --git a/app/code/Magento/AsynchronousOperations/README.md b/app/code/Magento/AsynchronousOperations/README.md new file mode 100644 index 0000000000000..fb7d53df1b81c --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/README.md @@ -0,0 +1 @@ + This component is designed to provide response for client who launched the bulk operation as soon as possible and postpone handling of operations moving them to background handler. \ No newline at end of file diff --git a/app/code/Magento/AsynchronousOperations/Test/Mftf/README.md b/app/code/Magento/AsynchronousOperations/Test/Mftf/README.md new file mode 100644 index 0000000000000..2f73e44149f2c --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Asynchronous Operations Functional Tests + +The Functional Test Module for **Magento Asynchronous Operations** module. diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/BackButtonTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/BackButtonTest.php new file mode 100644 index 0000000000000..d6d4c5e7479f9 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/BackButtonTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Block\Adminhtml\Bulk\Details; + +class BackButtonTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\BackButton + */ + protected $block; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $urlBuilderMock; + + protected function setUp() + { + $this->urlBuilderMock = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) + ->getMock(); + $this->block = new \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\BackButton( + $this->urlBuilderMock + ); + } + + public function testGetButtonData() + { + $backUrl = 'back url'; + $expectedResult = [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $backUrl), + 'class' => 'back', + 'sort_order' => 10 + ]; + + $this->urlBuilderMock->expects($this->once()) + ->method('getUrl') + ->with('*/') + ->willReturn($backUrl); + + $this->assertEquals($expectedResult, $this->block->getButtonData()); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/DoneButtonTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/DoneButtonTest.php new file mode 100644 index 0000000000000..10c9d898aa526 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/DoneButtonTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Block\Adminhtml\Bulk\Details; + +use Magento\Framework\Bulk\OperationInterface; + +class DoneButtonTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\DoneButton + */ + protected $block; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $bulkStatusMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $requestMock; + + protected function setUp() + { + $this->bulkStatusMock = $this->createMock(\Magento\Framework\Bulk\BulkStatusInterface::class); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->getMock(); + $this->block = new \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\DoneButton( + $this->bulkStatusMock, + $this->requestMock + ); + } + + /** + * @param int $failedCount + * @param int $buttonsParam + * @param array $expectedResult + * @dataProvider getButtonDataProvider + */ + public function testGetButtonData($failedCount, $buttonsParam, $expectedResult) + { + $uuid = 'some standard uuid string'; + $this->requestMock->expects($this->exactly(2)) + ->method('getParam') + ->withConsecutive(['uuid'], ['buttons']) + ->willReturnOnConsecutiveCalls($uuid, $buttonsParam); + $this->bulkStatusMock->expects($this->once()) + ->method('getOperationsCountByBulkIdAndStatus') + ->with($uuid, OperationInterface::STATUS_TYPE_RETRIABLY_FAILED) + ->willReturn($failedCount); + + $this->assertEquals($expectedResult, $this->block->getButtonData()); + } + + /** + * @return array + */ + public function getButtonDataProvider() + { + return [ + [1, 0, []], + [0, 0, []], + [ + 0, + 1, + [ + 'label' => __('Done'), + 'class' => 'primary', + 'sort_order' => 10, + 'on_click' => '', + 'data_attribute' => [ + 'mage-init' => [ + 'Magento_Ui/js/form/button-adapter' => [ + 'actions' => [ + [ + 'targetName' => 'notification_area.notification_area.modalContainer.modal', + 'actionName' => 'closeModal' + ], + ], + ], + ], + ], + ] + ], + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/RetryButtonTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/RetryButtonTest.php new file mode 100644 index 0000000000000..b7c154be09d89 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Block/Adminhtml/Bulk/Details/RetryButtonTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Block\Adminhtml\Bulk\Details; + +class RetryButtonTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\RetryButton + */ + protected $block; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $detailsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $requestMock; + + protected function setUp() + { + $this->detailsMock = $this->getMockBuilder(\Magento\AsynchronousOperations\Model\Operation\Details::class) + ->disableOriginalConstructor() + ->getMock(); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->getMock(); + $this->block = new \Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\RetryButton( + $this->detailsMock, + $this->requestMock + ); + } + + /** + * @param int $failedCount + * @param array $expectedResult + * @dataProvider getButtonDataProvider + */ + public function testGetButtonData($failedCount, $expectedResult) + { + $details = ['failed_retriable' => $failedCount]; + $uuid = 'some standard uuid string'; + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('uuid') + ->willReturn($uuid); + $this->detailsMock->expects($this->once()) + ->method('getDetails') + ->with($uuid) + ->willReturn($details); + + $this->assertEquals($expectedResult, $this->block->getButtonData()); + } + + /** + * @return array + */ + public function getButtonDataProvider() + { + return [ + [0, []], + [ + 20, + [ + 'label' => __('Retry'), + 'class' => 'retry primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + ] + ], + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/DetailsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/DetailsTest.php new file mode 100644 index 0000000000000..ecd33d355c223 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/DetailsTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Controller\Adminhtml\Bulk; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DetailsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $viewMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \Magento\AsynchronousOperations\Controller\Adminhtml\Bulk\Details + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->viewMock = $this->createMock(\Magento\Framework\App\ViewInterface::class); + $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + $this->resultFactoryMock = $this->createMock(\Magento\Framework\View\Result\PageFactory::class); + $this->model = $objectManager->getObject( + \Magento\AsynchronousOperations\Controller\Adminhtml\Bulk\Details::class, + [ + 'request' => $this->requestMock, + 'resultPageFactory' => $this->resultFactoryMock, + 'view' => $this->viewMock, + + ] + ); + } + + public function testExecute() + { + $id = '42'; + $parameterName = 'uuid'; + $itemId = 'Magento_AsynchronousOperations::system_magento_logging_bulk_operations'; + $layoutMock = $this->createMock(\Magento\Framework\View\LayoutInterface::class); + + $blockMock = $this->createPartialMock( + \Magento\Framework\View\Element\BlockInterface::class, + ['setActive', 'getMenuModel', 'toHtml'] + ); + $menuModelMock = $this->createMock(\Magento\Backend\Model\Menu::class); + $this->viewMock->expects($this->once())->method('getLayout')->willReturn($layoutMock); + $layoutMock->expects($this->once())->method('getBlock')->willReturn($blockMock); + $blockMock->expects($this->once())->method('setActive')->with($itemId); + $blockMock->expects($this->once())->method('getMenuModel')->willReturn($menuModelMock); + $menuModelMock->expects($this->once())->method('getParentItems')->willReturn([]); + $pageMock = $this->createMock(\Magento\Framework\View\Result\Page::class); + $pageConfigMock = $this->createMock(\Magento\Framework\View\Page\Config::class); + $titleMock = $this->createMock(\Magento\Framework\View\Page\Title::class); + $this->resultFactoryMock->expects($this->once())->method('create')->willReturn($pageMock); + $this->requestMock->expects($this->once())->method('getParam')->with($parameterName)->willReturn($id); + $pageMock->expects($this->once())->method('getConfig')->willReturn($pageConfigMock); + $pageConfigMock->expects($this->once())->method('getTitle')->willReturn($titleMock); + $titleMock->expects($this->once())->method('prepend')->with($this->stringContains($id)); + $pageMock->expects($this->once())->method('initLayout'); + $this->assertEquals($pageMock, $this->model->execute()); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/RetryTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/RetryTest.php new file mode 100644 index 0000000000000..ab5e117b0225f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Bulk/RetryTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Controller\Adminhtml\Bulk; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\RequestInterface; +use Magento\Backend\Model\View\Result\RedirectFactory; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\AsynchronousOperations\Controller\Adminhtml\Bulk\Retry; +use Magento\AsynchronousOperations\Model\BulkManagement; +use Magento\AsynchronousOperations\Model\BulkNotificationManagement; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\Result\Json; + +class RetryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Retry + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkManagementMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $notificationManagementMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $jsonResultMock; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->bulkManagementMock = $this->createMock(BulkManagement::class); + $this->notificationManagementMock = $this->createMock(BulkNotificationManagement::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->resultFactoryMock = $this->createPartialMock(ResultFactory::class, ['create']); + $this->jsonResultMock = $this->createMock(Json::class); + + $this->resultRedirectFactoryMock = $this->createPartialMock(RedirectFactory::class, ['create']); + $this->resultRedirectMock = $this->createMock(Redirect::class); + + $this->model = $objectManager->getObject( + Retry::class, + [ + 'bulkManagement' => $this->bulkManagementMock, + 'notificationManagement' => $this->notificationManagementMock, + 'request' => $this->requestMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock, + 'resultFactory' => $this->resultFactoryMock, + ] + ); + } + + public function testExecute() + { + $bulkUuid = '49da7406-1ec3-4100-95ae-9654c83a6801'; + $operationsToRetry = [ + [ + 'key' => 'value', + 'error_code' => 1111, + ], + [ + 'error_code' => 2222, + ], + [ + 'error_code' => '3333', + ], + ]; + + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['uuid', null, $bulkUuid], + ['operations_to_retry', [], $operationsToRetry], + ['isAjax', null, false], + ]); + + $this->bulkManagementMock->expects($this->once()) + ->method('retryBulk') + ->with($bulkUuid, [1111, 2222, 3333]); + + $this->notificationManagementMock->expects($this->once()) + ->method('ignoreBulks') + ->with([$bulkUuid]) + ->willReturn(true); + + $this->resultRedirectFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->resultRedirectMock); + + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('bulk/index'); + + $this->model->execute(); + } + + public function testExecuteReturnsJsonResultWhenRequestIsSentViaAjax() + { + $bulkUuid = '49da7406-1ec3-4100-95ae-9654c83a6801'; + $operationsToRetry = [ + [ + 'key' => 'value', + 'error_code' => 1111, + ], + ]; + + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['uuid', null, $bulkUuid], + ['operations_to_retry', [], $operationsToRetry], + ['isAjax', null, true], + ]); + + $this->bulkManagementMock->expects($this->once()) + ->method('retryBulk') + ->with($bulkUuid, [1111]); + + $this->notificationManagementMock->expects($this->once()) + ->method('ignoreBulks') + ->with([$bulkUuid]) + ->willReturn(true); + + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON, []) + ->willReturn($this->jsonResultMock); + + $this->jsonResultMock->expects($this->once()) + ->method('setHttpResponseCode') + ->with(200); + + $this->assertEquals($this->jsonResultMock, $this->model->execute()); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Index/IndexTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Index/IndexTest.php new file mode 100644 index 0000000000000..98d51d8b0fd46 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Index/IndexTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Controller\Adminhtml\Index; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class IndexTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $viewMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \Magento\AsynchronousOperations\Controller\Adminhtml\Index\Index + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->viewMock = $this->createMock(\Magento\Framework\App\ViewInterface::class); + $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + $this->resultFactoryMock = $this->createMock(\Magento\Framework\View\Result\PageFactory::class); + + $this->model = $objectManager->getObject( + \Magento\AsynchronousOperations\Controller\Adminhtml\Index\Index::class, + [ + 'request' => $this->requestMock, + 'view' => $this->viewMock, + 'resultPageFactory' => $this->resultFactoryMock + + ] + ); + } + + public function testExecute() + { + $itemId = 'Magento_AsynchronousOperations::system_magento_logging_bulk_operations'; + $prependText = 'Bulk Actions Log'; + $layoutMock = $this->createMock(\Magento\Framework\View\LayoutInterface::class); + $menuModelMock = $this->createMock(\Magento\Backend\Model\Menu::class); + $pageMock = $this->createMock(\Magento\Framework\View\Result\Page::class); + $pageConfigMock = $this->createMock(\Magento\Framework\View\Page\Config::class); + $titleMock = $this->createMock(\Magento\Framework\View\Page\Title::class); + $this->resultFactoryMock->expects($this->once())->method('create')->willReturn($pageMock); + + $blockMock = $this->createPartialMock( + \Magento\Framework\View\Element\BlockInterface::class, + ['setActive', 'getMenuModel', 'toHtml'] + ); + + $this->viewMock->expects($this->once())->method('getLayout')->willReturn($layoutMock); + $layoutMock->expects($this->once())->method('getBlock')->willReturn($blockMock); + $blockMock->expects($this->once())->method('setActive')->with($itemId); + $blockMock->expects($this->once())->method('getMenuModel')->willReturn($menuModelMock); + $menuModelMock->expects($this->once())->method('getParentItems')->willReturn([]); + + $pageMock->expects($this->once())->method('getConfig')->willReturn($pageConfigMock); + $pageConfigMock->expects($this->once())->method('getTitle')->willReturn($titleMock); + $titleMock->expects($this->once())->method('prepend')->with($prependText); + $pageMock->expects($this->once())->method('initLayout'); + $this->model->execute(); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php new file mode 100644 index 0000000000000..8ec1ec4609aa9 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Controller\Adminhtml\Notification; + +use Magento\AsynchronousOperations\Model\BulkNotificationManagement; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\AsynchronousOperations\Controller\Adminhtml\Notification\Dismiss; +use Magento\Framework\Controller\Result\Json; + +class DismissTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Dismiss + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $notificationManagementMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $jsonResultMock; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->notificationManagementMock = $this->createMock(BulkNotificationManagement::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->resultFactoryMock = $this->createPartialMock(ResultFactory::class, ['create']); + + $this->jsonResultMock = $this->createMock(Json::class); + + $this->model = $objectManager->getObject( + Dismiss::class, + [ + 'notificationManagement' => $this->notificationManagementMock, + 'request' => $this->requestMock, + 'resultFactory' => $this->resultFactoryMock, + ] + ); + } + + public function testExecute() + { + $bulkUuids = ['49da7406-1ec3-4100-95ae-9654c83a6801']; + + $this->requestMock->expects($this->any()) + ->method('getParam') + ->with('uuid', []) + ->willReturn($bulkUuids); + + $this->notificationManagementMock->expects($this->once()) + ->method('acknowledgeBulks') + ->with($bulkUuids) + ->willReturn(true); + + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON, []) + ->willReturn($this->jsonResultMock); + + $this->assertEquals($this->jsonResultMock, $this->model->execute()); + } + + public function testExecuteSetsBadRequestResponseStatusIfBulkWasNotAcknowledgedCorrectly() + { + $bulkUuids = ['49da7406-1ec3-4100-95ae-9654c83a6801']; + + $this->requestMock->expects($this->any()) + ->method('getParam') + ->with('uuid', []) + ->willReturn($bulkUuids); + + $this->resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON, []) + ->willReturn($this->jsonResultMock); + + $this->notificationManagementMock->expects($this->once()) + ->method('acknowledgeBulks') + ->with($bulkUuids) + ->willReturn(false); + + $this->jsonResultMock->expects($this->once()) + ->method('setHttpResponseCode') + ->with(400); + + $this->assertEquals($this->jsonResultMock, $this->model->execute()); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Cron/BulkCleanupTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Cron/BulkCleanupTest.php new file mode 100644 index 0000000000000..be38e9181734a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Cron/BulkCleanupTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Controller\Cron; + +class BulkCleanupTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $dateTimeMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var \Magento\AsynchronousOperations\Cron\BulkCleanup + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $timeMock; + + protected function setUp() + { + $this->dateTimeMock = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); + $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->resourceConnectionMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->timeMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\DateTime::class); + $this->model = new \Magento\AsynchronousOperations\Cron\BulkCleanup( + $this->metadataPoolMock, + $this->resourceConnectionMock, + $this->dateTimeMock, + $this->scopeConfigMock, + $this->timeMock + ); + } + + public function testExecute() + { + $entityType = 'BulkSummaryInterface'; + $connectionName = 'Connection'; + $bulkLifetimeMultiplier = 10; + $bulkLifetime = 3600 * 24 * $bulkLifetimeMultiplier; + + $adapterMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $entityMetadataMock = $this->createMock(\Magento\Framework\EntityManager\EntityMetadataInterface::class); + + $this->metadataPoolMock->expects($this->once())->method('getMetadata')->with($this->stringContains($entityType)) + ->willReturn($entityMetadataMock); + $entityMetadataMock->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $this->resourceConnectionMock->expects($this->once())->method('getConnectionByName')->with($connectionName) + ->willReturn($adapterMock); + $this->scopeConfigMock->expects($this->once())->method('getValue')->with($this->stringContains('bulk/lifetime')) + ->willReturn($bulkLifetimeMultiplier); + $this->timeMock->expects($this->once())->method('gmtTimestamp')->willReturn($bulkLifetime*10); + $this->dateTimeMock->expects($this->once())->method('formatDate')->with($bulkLifetime*9); + $adapterMock->expects($this->once())->method('delete'); + + $this->model->execute(); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/AccessValidatorTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/AccessValidatorTest.php new file mode 100644 index 0000000000000..8eb8778a384b0 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/AccessValidatorTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model; + +class AccessValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\AccessValidator + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $userContextMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkSummaryFactoryMock; + + protected function setUp() + { + $this->userContextMock = $this->createMock(\Magento\Authorization\Model\UserContextInterface::class); + $this->entityManagerMock = $this->createMock(\Magento\Framework\EntityManager\EntityManager::class); + $this->bulkSummaryFactoryMock = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory::class, + ['create'] + ); + + $this->model = new \Magento\AsynchronousOperations\Model\AccessValidator( + $this->userContextMock, + $this->entityManagerMock, + $this->bulkSummaryFactoryMock + ); + } + + /** + * @dataProvider summaryDataProvider + * @param string $bulkUserId + * @param bool $expectedResult + */ + public function testIsAllowed($bulkUserId, $expectedResult) + { + $adminId = 1; + $uuid = 'test-001'; + $bulkSummaryMock = $this->createMock(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class); + + $this->bulkSummaryFactoryMock->expects($this->once())->method('create')->willReturn($bulkSummaryMock); + $this->entityManagerMock->expects($this->once()) + ->method('load') + ->with($bulkSummaryMock, $uuid) + ->willReturn($bulkSummaryMock); + + $bulkSummaryMock->expects($this->once())->method('getUserId')->willReturn($bulkUserId); + $this->userContextMock->expects($this->once())->method('getUserId')->willReturn($adminId); + + $this->assertEquals($this->model->isAllowed($uuid), $expectedResult); + } + + /** + * @return array + */ + public static function summaryDataProvider() + { + return [ + [2, false], + [1, true] + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkDescription/OptionsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkDescription/OptionsTest.php new file mode 100644 index 0000000000000..d5835f7856dff --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkDescription/OptionsTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model\BulkDescription; + +class OptionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\BulkDescription\Options + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkCollectionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $userContextMock; + + protected function setUp() + { + $this->bulkCollectionFactoryMock = $this->createPartialMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory::class, + ['create'] + ); + $this->userContextMock = $this->createMock(\Magento\Authorization\Model\UserContextInterface::class); + $this->model = new \Magento\AsynchronousOperations\Model\BulkDescription\Options( + $this->bulkCollectionFactoryMock, + $this->userContextMock + ); + } + + public function testToOptionsArray() + { + $userId = 100; + $collectionMock = $this->createMock(\Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection::class); + $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $this->bulkCollectionFactoryMock->expects($this->once())->method('create')->willReturn($collectionMock); + + $this->userContextMock->expects($this->once())->method('getUserId')->willReturn($userId); + + $collectionMock->expects($this->once())->method('getMainTable')->willReturn('table'); + + $selectMock->expects($this->once())->method('reset')->willReturnSelf(); + $selectMock->expects($this->once())->method('distinct')->with(true)->willReturnSelf(); + $selectMock->expects($this->once())->method('from')->with('table', ['description'])->willReturnSelf(); + $selectMock->expects($this->once())->method('where')->with('user_id = ?', $userId)->willReturnSelf(); + + $itemMock = $this->createPartialMock( + \Magento\AsynchronousOperations\Model\BulkSummary::class, + ['getDescription'] + ); + $itemMock->expects($this->exactly(2))->method('getDescription')->willReturn('description'); + + $collectionMock->expects($this->once())->method('getSelect')->willReturn($selectMock); + $collectionMock->expects($this->once())->method('getItems')->willReturn([$itemMock]); + + $expectedResult = [ + [ + 'value' => 'description', + 'label' => 'description' + ] + ]; + + $this->assertEquals($expectedResult, $this->model->toOptionArray()); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php new file mode 100644 index 0000000000000..e5951fb129e7e --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php @@ -0,0 +1,298 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model; + +/** + * Unit test for BulkManagement model. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BulkManagementTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\EntityManager\EntityManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $entityManager; + + /** + * @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory + * |\PHPUnit_Framework_MockObject_MockObject + */ + private $bulkSummaryFactory; + + /** + * @var \Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory + * |\PHPUnit_Framework_MockObject_MockObject + */ + private $operationCollectionFactory; + + /** + * @var \Magento\Framework\MessageQueue\BulkPublisherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $publisher; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; + + /** + * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $logger; + + /** + * @var \Magento\AsynchronousOperations\Model\BulkManagement + */ + private $bulkManagement; + + /** + * Set up. + * + * @return void + */ + protected function setUp() + { + $this->entityManager = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityManager::class) + ->disableOriginalConstructor()->getMock(); + $this->bulkSummaryFactory = $this + ->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->operationCollectionFactory = $this + ->getMockBuilder(\Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->publisher = $this->getMockBuilder(\Magento\Framework\MessageQueue\BulkPublisherInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor()->getMock(); + $this->logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor()->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->bulkManagement = $objectManager->getObject( + \Magento\AsynchronousOperations\Model\BulkManagement::class, + [ + 'entityManager' => $this->entityManager, + 'bulkSummaryFactory' => $this->bulkSummaryFactory, + 'operationCollectionFactory' => $this->operationCollectionFactory, + 'publisher' => $this->publisher, + 'metadataPool' => $this->metadataPool, + 'resourceConnection' => $this->resourceConnection, + 'logger' => $this->logger, + ] + ); + } + + /** + * Test for scheduleBulk method. + * + * @return void + */ + public function testScheduleBulk() + { + $bulkUuid = 'bulk-001'; + $description = 'Bulk summary description...'; + $userId = 1; + $userType = \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN; + $connectionName = 'default'; + $topicNames = ['topic.name.0', 'topic.name.1']; + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->disableOriginalConstructor()->getMock(); + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnectionByName')->with($connectionName)->willReturn($connection); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $bulkSummary = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->bulkSummaryFactory->expects($this->once())->method('create')->willReturn($bulkSummary); + $this->entityManager->expects($this->once()) + ->method('load')->with($bulkSummary, $bulkUuid)->willReturn($bulkSummary); + $bulkSummary->expects($this->once())->method('setBulkId')->with($bulkUuid)->willReturnSelf(); + $bulkSummary->expects($this->once())->method('setDescription')->with($description)->willReturnSelf(); + $bulkSummary->expects($this->once())->method('setUserId')->with($userId)->willReturnSelf(); + $bulkSummary->expects($this->once())->method('setUserType')->with($userType)->willReturnSelf(); + $bulkSummary->expects($this->once())->method('getOperationCount')->willReturn(1); + $bulkSummary->expects($this->once())->method('setOperationCount')->with(3)->willReturnSelf(); + $this->entityManager->expects($this->once())->method('save')->with($bulkSummary)->willReturn($bulkSummary); + $connection->expects($this->once())->method('commit')->willReturnSelf(); + $operation->expects($this->exactly(2))->method('getTopicName') + ->willReturnOnConsecutiveCalls($topicNames[0], $topicNames[1]); + $this->publisher->expects($this->exactly(2))->method('publish') + ->withConsecutive([$topicNames[0], [$operation]], [$topicNames[1], [$operation]])->willReturn(null); + $this->assertTrue( + $this->bulkManagement->scheduleBulk($bulkUuid, [$operation, $operation], $description, $userId) + ); + } + + /** + * Test for scheduleBulk method with exception. + * + * @return void + */ + public function testScheduleBulkWithException() + { + $bulkUuid = 'bulk-001'; + $description = 'Bulk summary description...'; + $userId = 1; + $connectionName = 'default'; + $exceptionMessage = 'Exception message'; + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->disableOriginalConstructor()->getMock(); + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnectionByName')->with($connectionName)->willReturn($connection); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $bulkSummary = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->bulkSummaryFactory->expects($this->once())->method('create')->willReturn($bulkSummary); + $this->entityManager->expects($this->once())->method('load') + ->with($bulkSummary, $bulkUuid)->willThrowException(new \LogicException($exceptionMessage)); + $connection->expects($this->once())->method('rollBack')->willReturnSelf(); + $this->logger->expects($this->once())->method('critical')->with($exceptionMessage); + $this->publisher->expects($this->never())->method('publish'); + $this->assertFalse($this->bulkManagement->scheduleBulk($bulkUuid, [$operation], $description, $userId)); + } + + /** + * Test for retryBulk method. + * + * @return void + */ + public function testRetryBulk() + { + $bulkUuid = 'bulk-001'; + $errorCodes = ['errorCode']; + $connectionName = 'default'; + $operationId = 1; + $operationTable = 'magento_operation'; + $topicName = 'topic.name'; + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnectionByName')->with($connectionName)->willReturn($connection); + $operationCollection = $this + ->getMockBuilder(\Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class) + ->disableOriginalConstructor()->getMock(); + $this->operationCollectionFactory->expects($this->once())->method('create')->willReturn($operationCollection); + $operationCollection->expects($this->exactly(2))->method('addFieldToFilter') + ->withConsecutive(['error_code', ['in' => $errorCodes]], ['bulk_uuid', ['eq' => $bulkUuid]]) + ->willReturnSelf(); + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->disableOriginalConstructor()->getMock(); + $operationCollection->expects($this->once())->method('getItems')->willReturn([$operation]); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $operation->expects($this->once())->method('getId')->willReturn($operationId); + $operation->expects($this->once())->method('setId')->with(null)->willReturnSelf(); + $this->resourceConnection->expects($this->once()) + ->method('getTableName')->with($operationTable)->willReturn($operationTable); + $connection->expects($this->once()) + ->method('quoteInto')->with('id IN (?)', [$operationId])->willReturn('id IN (' . $operationId .')'); + $connection->expects($this->once()) + ->method('delete')->with($operationTable, 'id IN (' . $operationId .')')->willReturn(1); + $connection->expects($this->once())->method('commit')->willReturnSelf(); + $operation->expects($this->once())->method('getTopicName')->willReturn($topicName); + $this->publisher->expects($this->once())->method('publish')->with($topicName, [$operation])->willReturn(null); + $this->assertEquals(1, $this->bulkManagement->retryBulk($bulkUuid, $errorCodes)); + } + + /** + * Test for retryBulk method with exception. + * + * @return void + */ + public function testRetryBulkWithException() + { + $bulkUuid = 'bulk-001'; + $errorCodes = ['errorCode']; + $connectionName = 'default'; + $operationId = 1; + $operationTable = 'magento_operation'; + $exceptionMessage = 'Exception message'; + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnectionByName')->with($connectionName)->willReturn($connection); + $operationCollection = $this + ->getMockBuilder(\Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class) + ->disableOriginalConstructor()->getMock(); + $this->operationCollectionFactory->expects($this->once())->method('create')->willReturn($operationCollection); + $operationCollection->expects($this->exactly(2))->method('addFieldToFilter') + ->withConsecutive(['error_code', ['in' => $errorCodes]], ['bulk_uuid', ['eq' => $bulkUuid]]) + ->willReturnSelf(); + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->disableOriginalConstructor()->getMock(); + $operationCollection->expects($this->once())->method('getItems')->willReturn([$operation]); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $operation->expects($this->once())->method('getId')->willReturn($operationId); + $operation->expects($this->once())->method('setId')->with(null)->willReturnSelf(); + $this->resourceConnection->expects($this->once()) + ->method('getTableName')->with($operationTable)->willReturn($operationTable); + $connection->expects($this->once()) + ->method('quoteInto')->with('id IN (?)', [$operationId])->willReturn('id IN (' . $operationId .')'); + $connection->expects($this->once()) + ->method('delete')->with($operationTable, 'id IN (' . $operationId .')') + ->willThrowException(new \Exception($exceptionMessage)); + $connection->expects($this->once())->method('rollBack')->willReturnSelf(); + $this->logger->expects($this->once())->method('critical')->with($exceptionMessage); + $this->publisher->expects($this->never())->method('publish'); + $this->assertEquals(0, $this->bulkManagement->retryBulk($bulkUuid, $errorCodes)); + } + + /** + * Test for deleteBulk method. + * + * @return void + */ + public function testDeleteBulk() + { + $bulkUuid = 'bulk-001'; + $bulkSummary = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->bulkSummaryFactory->expects($this->once())->method('create')->willReturn($bulkSummary); + $this->entityManager->expects($this->once()) + ->method('load')->with($bulkSummary, $bulkUuid)->willReturn($bulkSummary); + $this->entityManager->expects($this->once())->method('delete')->with($bulkSummary)->willReturn(true); + $this->assertTrue($this->bulkManagement->deleteBulk($bulkUuid)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php new file mode 100644 index 0000000000000..a5a75736d2441 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php @@ -0,0 +1,270 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BulkStatusTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\BulkStatus + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkCollectionFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationCollectionFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $calculatedStatusSqlMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkDetailedFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkShortFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityMetadataMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityManager; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + protected function setUp() + { + $this->bulkCollectionFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory::class, + ['create'] + ); + $this->operationCollectionFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory::class, + ['create'] + ); + $this->operationMock = $this->createMock(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class); + $this->bulkMock = $this->createMock(\Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class); + $this->resourceConnectionMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->calculatedStatusSqlMock = $this->createMock( + \Magento\AsynchronousOperations\Model\BulkStatus\CalculatedStatusSql::class + ); + $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->bulkDetailedFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterfaceFactory ::class, + ['create'] + ); + $this->bulkShortFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterfaceFactory::class, + ['create'] + ); + $this->entityManager = $this->createMock(\Magento\Framework\EntityManager\EntityManager::class); + + $this->entityMetadataMock = $this->createMock(\Magento\Framework\EntityManager\EntityMetadataInterface::class); + $this->connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + + $this->model = new \Magento\AsynchronousOperations\Model\BulkStatus( + $this->bulkCollectionFactory, + $this->operationCollectionFactory, + $this->resourceConnectionMock, + $this->calculatedStatusSqlMock, + $this->metadataPoolMock, + $this->bulkDetailedFactory, + $this->bulkShortFactory, + $this->entityManager + ); + } + + /** + * @param int|null $failureType + * @param array $failureCodes + * @dataProvider getFailedOperationsByBulkIdDataProvider + */ + public function testGetFailedOperationsByBulkId($failureType, $failureCodes) + { + $bulkUuid = 'bulk-1'; + $operationCollection = $this->createMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class + ); + $this->operationCollectionFactory->expects($this->once())->method('create')->willReturn($operationCollection); + $operationCollection + ->expects($this->at(0)) + ->method('addFieldToFilter') + ->with('bulk_uuid', $bulkUuid) + ->willReturnSelf(); + $operationCollection + ->expects($this->at(1)) + ->method('addFieldToFilter') + ->with('status', $failureCodes) + ->willReturnSelf(); + $operationCollection->expects($this->once())->method('getItems')->willReturn([$this->operationMock]); + $this->assertEquals([$this->operationMock], $this->model->getFailedOperationsByBulkId($bulkUuid, $failureType)); + } + + public function testGetOperationsCountByBulkIdAndStatus() + { + $bulkUuid = 'bulk-1'; + $status = 1354; + $size = 32; + + $operationCollection = $this->createMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class + ); + $this->operationCollectionFactory->expects($this->once())->method('create')->willReturn($operationCollection); + $operationCollection + ->expects($this->at(0)) + ->method('addFieldToFilter') + ->with('bulk_uuid', $bulkUuid) + ->willReturnSelf(); + $operationCollection + ->expects($this->at(1)) + ->method('addFieldToFilter') + ->with('status', $status) + ->willReturnSelf(); + $operationCollection + ->expects($this->once()) + ->method('getSize') + ->willReturn($size); + $this->assertEquals($size, $this->model->getOperationsCountByBulkIdAndStatus($bulkUuid, $status)); + } + + /** + * @return array + */ + public function getFailedOperationsByBulkIdDataProvider() + { + return [ + [1, [1]], + [ + null, + [ + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, + ], + ], + ]; + } + + public function testGetBulksByUser() + { + $userId = 1; + $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $bulkCollection = $this->createMock(\Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection::class); + $bulkCollection->expects($this->once())->method('getSelect')->willReturn($selectMock); + $selectMock->expects($this->once())->method('columns')->willReturnSelf(); + $selectMock->expects($this->once())->method('order')->willReturnSelf(); + $this->bulkCollectionFactory->expects($this->once())->method('create')->willReturn($bulkCollection); + $bulkCollection->expects($this->once())->method('addFieldToFilter')->with('user_id', $userId)->willReturnSelf(); + $bulkCollection->expects($this->once())->method('getItems')->willReturn([$this->bulkMock]); + $this->assertEquals([$this->bulkMock], $this->model->getBulksByUser($userId)); + } + + public function testGetBulksStatus() + { + $bulkUuid = 'bulk-1'; + $allProcessedOperationCollection = $this->createMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class + ); + + $completeOperationCollection = $this->createMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Collection::class + ); + + $connectionName = 'connection_name'; + $entityType = \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::class; + $this->metadataPoolMock + ->expects($this->once()) + ->method('getMetadata') + ->with($entityType) + ->willReturn($this->entityMetadataMock); + $this->entityMetadataMock + ->expects($this->once()) + ->method('getEntityConnectionName') + ->willReturn($connectionName); + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnectionByName') + ->with($connectionName) + ->willReturn($this->connectionMock); + + $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $selectMock->expects($this->once())->method('from')->willReturnSelf(); + $selectMock->expects($this->once())->method('where')->with('uuid = ?', $bulkUuid)->willReturnSelf(); + $this->connectionMock->expects($this->once())->method('select')->willReturn($selectMock); + $this->connectionMock->expects($this->once())->method('fetchOne')->with($selectMock)->willReturn(10); + + $this->operationCollectionFactory + ->expects($this->at(0)) + ->method('create') + ->willReturn($allProcessedOperationCollection); + $this->operationCollectionFactory + ->expects($this->at(1)) + ->method('create') + ->willReturn($completeOperationCollection); + $allProcessedOperationCollection + ->expects($this->once()) + ->method('addFieldToFilter') + ->with('bulk_uuid', $bulkUuid) + ->willReturnSelf(); + $allProcessedOperationCollection->expects($this->once())->method('getSize')->willReturn(5); + + $completeOperationCollection + ->expects($this->at(0)) + ->method('addFieldToFilter') + ->with('bulk_uuid', $bulkUuid) + ->willReturnSelf(); + $completeOperationCollection + ->expects($this->at(1)) + ->method('addFieldToFilter') + ->with('status', OperationInterface::STATUS_TYPE_COMPLETE) + ->willReturnSelf(); + $completeOperationCollection->expects($this->any())->method('getSize')->willReturn(5); + $this->assertEquals(BulkSummaryInterface::IN_PROGRESS, $this->model->getBulkStatus($bulkUuid)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Entity/BulkSummaryMapperTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Entity/BulkSummaryMapperTest.php new file mode 100644 index 0000000000000..9543911c037d8 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Entity/BulkSummaryMapperTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model\Entity; + +use Magento\AsynchronousOperations\Model\Entity\BulkSummaryMapper; + +/** + * Class BulkSummaryMapperTest + */ +class BulkSummaryMapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\Entity\BulkSummaryMapper + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityMetadataMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + protected function setUp() + { + $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->resourceConnectionMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->entityMetadataMock = $this->createMock(\Magento\Framework\EntityManager\EntityMetadataInterface::class); + $this->connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $this->selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $this->model = new BulkSummaryMapper( + $this->metadataPoolMock, + $this->resourceConnectionMock + ); + } + + /** + * @param int $identifier + * @param array|false $result + * @dataProvider entityToDatabaseDataProvider + */ + public function testEntityToDatabase($identifier, $result) + { + $entityType = 'entityType'; + $data = ['uuid' => 'bulk-1']; + $connectionName = 'connection_name'; + $entityTable = 'table_name'; + $this->metadataPoolMock + ->expects($this->once()) + ->method('getMetadata') + ->with($entityType) + ->willReturn($this->entityMetadataMock); + $this->entityMetadataMock + ->expects($this->once()) + ->method('getEntityConnectionName') + ->willReturn($connectionName); + + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnectionByName') + ->with($connectionName) + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->entityMetadataMock->expects($this->once())->method('getEntityTable')->willReturn($entityTable); + $this->selectMock->expects($this->once())->method('from')->with($entityTable, 'id')->willReturnSelf(); + $this->selectMock->expects($this->once())->method('where')->with("uuid = ?", 'bulk-1')->willReturnSelf(); + $this->connectionMock + ->expects($this->once()) + ->method('fetchOne') + ->with($this->selectMock) + ->willReturn($identifier); + + $this->assertEquals($result, $this->model->entityToDatabase($entityType, $data)); + } + + /** + * @return array + */ + public function entityToDatabaseDataProvider() + { + return [ + [1, ['uuid' => 'bulk-1', 'id' => 1]], + [false, ['uuid' => 'bulk-1']] + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php new file mode 100644 index 0000000000000..f62e2b7f9d5ea --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model\Operation; + +use Magento\Framework\Bulk\OperationInterface; + +class DetailsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkStatusMock; + + /** + * @var \Magento\AsynchronousOperations\Model\Operation\Details + */ + private $model; + + protected function setUp() + { + $this->bulkStatusMock = $this->getMockBuilder(\Magento\Framework\Bulk\BulkStatusInterface::class) + ->getMock(); + $this->model = new \Magento\AsynchronousOperations\Model\Operation\Details($this->bulkStatusMock); + } + + public function testGetDetails() + { + $uuid = 'some_uuid_string'; + $completed = 100; + $failedRetriable = 23; + $failedNotRetriable = 45; + $open = 303; + $rejected = 0; + + $expectedResult = [ + 'operations_total' => $completed + $failedRetriable + $failedNotRetriable + $open, + 'operations_successful' => $completed, + 'operations_failed' => $failedRetriable + $failedNotRetriable, + 'failed_retriable' => $failedRetriable, + 'failed_not_retriable' => $failedNotRetriable, + 'rejected' => $rejected, + 'open' => $open, + ]; + + $this->bulkStatusMock->method('getOperationsCountByBulkIdAndStatus') + ->willReturnMap([ + [$uuid, OperationInterface::STATUS_TYPE_COMPLETE, $completed], + [$uuid, OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, $failedRetriable], + [$uuid, OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, $failedNotRetriable], + [$uuid, OperationInterface::STATUS_TYPE_OPEN, $open], + [$uuid, OperationInterface::STATUS_TYPE_REJECTED, $rejected], + ]); + + $result = $this->model->getDetails($uuid); + $this->assertEquals($expectedResult, $result); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php new file mode 100644 index 0000000000000..4c3e240da2a8a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Model; + +/** + * Class OperationManagementTest + */ +class OperationManagementTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\OperationManagement + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + protected function setUp() + { + $this->entityManagerMock = $this->createMock(\Magento\Framework\EntityManager\EntityManager::class); + $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->operationFactoryMock = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory::class, + ['create'] + ); + $this->operationMock = + $this->createMock(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class); + $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); + $this->model = new \Magento\AsynchronousOperations\Model\OperationManagement( + $this->entityManagerMock, + $this->operationFactoryMock, + $this->loggerMock + ); + } + + public function testChangeOperationStatus() + { + $operationId = 1; + $status = 1; + $message = 'Message'; + $data = 'data'; + $errorCode = 101; + $this->operationFactoryMock->expects($this->once())->method('create')->willReturn($this->operationMock); + $this->entityManagerMock->expects($this->once())->method('load')->with($this->operationMock, $operationId); + $this->operationMock->expects($this->once())->method('setStatus')->with($status)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setResultMessage')->with($message)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setSerializedData')->with($data)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setErrorCode')->with($errorCode)->willReturnSelf(); + $this->entityManagerMock->expects($this->once())->method('save')->with($this->operationMock); + $this->assertTrue($this->model->changeOperationStatus($operationId, $status, $errorCode, $message, $data)); + } + + public function testChangeOperationStatusIfExceptionWasThrown() + { + $operationId = 1; + $status = 1; + $message = 'Message'; + $data = 'data'; + $errorCode = 101; + $this->operationFactoryMock->expects($this->once())->method('create')->willReturn($this->operationMock); + $this->entityManagerMock->expects($this->once())->method('load')->with($this->operationMock, $operationId); + $this->operationMock->expects($this->once())->method('setStatus')->with($status)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setResultMessage')->with($message)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setSerializedData')->with($data)->willReturnSelf(); + $this->operationMock->expects($this->once())->method('setErrorCode')->with($errorCode)->willReturnSelf(); + $this->entityManagerMock->expects($this->once())->method('save')->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once())->method('critical'); + $this->assertFalse($this->model->changeOperationStatus($operationId, $status, $errorCode, $message, $data)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/Operation/CreateTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/Operation/CreateTest.php new file mode 100644 index 0000000000000..2f0fc8ceba46f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/Operation/CreateTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model\ResourceModel\Operation; + +/** + * Unit test for Create operation. + */ +class CreateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\EntityManager\MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; + + /** + * @var \Magento\Framework\EntityManager\TypeResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $typeResolver; + + /** + * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @var \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Create + */ + private $create; + + /** + * Set up. + * + * @return void + */ + protected function setUp() + { + $this->metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor()->getMock(); + $this->typeResolver = $this->getMockBuilder(\Magento\Framework\EntityManager\TypeResolver::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor()->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->create = $objectManager->getObject( + \Magento\AsynchronousOperations\Model\ResourceModel\Operation\Create::class, + [ + 'metadataPool' => $this->metadataPool, + 'typeResolver' => $this->typeResolver, + 'resourceConnection' => $this->resourceConnection, + ] + ); + } + + /** + * Test for execute method. + * + * @return void + */ + public function testExecute() + { + $connectionName = 'default'; + $operationData = ['key1' => 'value1']; + $operationTable = 'magento_operation'; + $operationList = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->typeResolver->expects($this->once())->method('resolve')->with($operationList) + ->willReturn(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class); + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class)->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnection')->with($connectionName)->willReturn($connection); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->setMethods(['getData']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $operationList->expects($this->once())->method('getItems')->willReturn([$operation]); + $operation->expects($this->once())->method('getData')->willReturn($operationData); + $metadata->expects($this->once())->method('getEntityTable')->willReturn($operationTable); + $connection->expects($this->once())->method('insertOnDuplicate') + ->with($operationTable, [$operationData], ['status', 'error_code', 'result_message'])->willReturn(1); + $connection->expects($this->once())->method('commit')->willReturnSelf(); + $this->assertEquals($operationList, $this->create->execute($operationList)); + } + + /** + * Test for execute method with exception. + * + * @return void + * @expectedException \Exception + */ + public function testExecuteWithException() + { + $connectionName = 'default'; + $operationData = ['key1' => 'value1']; + $operationTable = 'magento_operation'; + $operationList = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->typeResolver->expects($this->once())->method('resolve')->with($operationList) + ->willReturn(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class); + $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(\Magento\AsynchronousOperations\Api\Data\OperationListInterface::class)->willReturn($metadata); + $metadata->expects($this->once())->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->resourceConnection->expects($this->once()) + ->method('getConnection')->with($connectionName)->willReturn($connection); + $connection->expects($this->once())->method('beginTransaction')->willReturnSelf(); + $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) + ->setMethods(['getData']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $operationList->expects($this->once())->method('getItems')->willReturn([$operation]); + $operation->expects($this->once())->method('getData')->willReturn($operationData); + $metadata->expects($this->once())->method('getEntityTable')->willReturn($operationTable); + $connection->expects($this->once())->method('insertOnDuplicate') + ->with($operationTable, [$operationData], ['status', 'error_code', 'result_message']) + ->willThrowException(new \Exception()); + $connection->expects($this->once())->method('rollBack')->willReturnSelf(); + $this->create->execute($operationList); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/System/Message/Collection/Synchronized/PluginTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/System/Message/Collection/Synchronized/PluginTest.php new file mode 100644 index 0000000000000..6a51258b34afc --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/ResourceModel/System/Message/Collection/Synchronized/PluginTest.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Model\ResourceModel\System\Message\Collection\Synchronized; + +use Magento\AsynchronousOperations\Model\ResourceModel\System\Message\Collection\Synchronized\Plugin; +use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\Bulk\BulkStatusInterface; +use Magento\AsynchronousOperations\Model\BulkNotificationManagement; +use Magento\AsynchronousOperations\Model\Operation\Details; +use Magento\Framework\AuthorizationInterface; +use Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized; + +/** + * Class PluginTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class PluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Plugin + */ + private $plugin; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $messagefactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkStatusMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkNotificationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $userContextMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationsDetailsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $authorizationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $messageMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $collectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $statusMapper; + + /** + * @var string + */ + private $resourceName = 'Magento_Logging::system_magento_logging_bulk_operations'; + + protected function setUp() + { + $this->messagefactoryMock = $this->createPartialMock( + \Magento\AdminNotification\Model\System\MessageFactory::class, + ['create'] + ); + $this->bulkStatusMock = $this->createMock(BulkStatusInterface::class); + + $this->userContextMock = $this->createMock(UserContextInterface::class); + $this->operationsDetailsMock = $this->createMock(Details::class); + $this->authorizationMock = $this->createMock(AuthorizationInterface::class); + $this->messageMock = $this->createMock(\Magento\AdminNotification\Model\System\Message::class); + $this->collectionMock = $this->createMock(Synchronized::class); + $this->bulkNotificationMock = $this->createMock(BulkNotificationManagement::class); + $this->statusMapper = $this->createMock(\Magento\AsynchronousOperations\Model\StatusMapper::class); + $this->plugin = new Plugin( + $this->messagefactoryMock, + $this->bulkStatusMock, + $this->bulkNotificationMock, + $this->userContextMock, + $this->operationsDetailsMock, + $this->authorizationMock, + $this->statusMapper + ); + } + + public function testAfterToArrayIfNotAllowed() + { + $result = []; + $this->authorizationMock + ->expects($this->once()) + ->method('isAllowed') + ->with($this->resourceName) + ->willReturn(false); + $this->assertEquals($result, $this->plugin->afterToArray($this->collectionMock, $result)); + } + + /** + * @param array $operationDetails + * @dataProvider afterToDataProvider + */ + public function testAfterTo($operationDetails) + { + $methods = ['getBulkId', 'getDescription', 'getStatus', 'getStartTime']; + $bulkMock = $this->createPartialMock(\Magento\AsynchronousOperations\Model\BulkSummary::class, $methods); + $result = ['items' =>[], 'totalRecords' => 1]; + $userBulks = [$bulkMock]; + $userId = 1; + $bulkUuid = 2; + $bulkArray = [ + 'status' => \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface::NOT_STARTED + ]; + $bulkMock->expects($this->once())->method('getBulkId')->willReturn($bulkUuid); + $this->operationsDetailsMock + ->expects($this->once()) + ->method('getDetails') + ->with($bulkUuid) + ->willReturn($operationDetails); + $bulkMock->expects($this->once())->method('getDescription')->willReturn('Bulk Description'); + $this->messagefactoryMock->expects($this->once())->method('create')->willReturn($this->messageMock); + $this->messageMock->expects($this->once())->method('toArray')->willReturn($bulkArray); + $this->authorizationMock + ->expects($this->once()) + ->method('isAllowed') + ->with($this->resourceName) + ->willReturn(true); + $this->userContextMock->expects($this->once())->method('getUserId')->willReturn($userId); + $this->bulkNotificationMock + ->expects($this->once()) + ->method('getAcknowledgedBulksByUser') + ->with($userId) + ->willReturn([]); + $this->statusMapper->expects($this->once())->method('operationStatusToBulkSummaryStatus'); + $this->bulkStatusMock->expects($this->once())->method('getBulksByUser')->willReturn($userBulks); + $result2 = $this->plugin->afterToArray($this->collectionMock, $result); + $this->assertEquals(2, $result2['totalRecords']); + } + + /** + * @return array + */ + public function afterToDataProvider() + { + return [ + ['operations_successful' => 0, + 'operations_failed' => 0, + 'operations_total' => 10 + ], + ['operations_successful' => 1, + 'operations_failed' => 2, + 'operations_total' => 10 + ], + ]; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php new file mode 100644 index 0000000000000..89fa80de36378 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Model; + +use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\Bulk\BulkSummaryInterface; + +/** + * Class StatusMapperTest + */ +class StatusMapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Model\StatusMapper + */ + private $model; + + protected function setUp() + { + $this->model = new \Magento\AsynchronousOperations\Model\StatusMapper(); + } + + public function testOperationStatusToBulkSummaryStatus() + { + $this->assertEquals( + $this->model->operationStatusToBulkSummaryStatus(OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED), + BulkSummaryInterface::FINISHED_WITH_FAILURE + ); + + $this->assertEquals( + $this->model->operationStatusToBulkSummaryStatus(OperationInterface::STATUS_TYPE_RETRIABLY_FAILED), + BulkSummaryInterface::FINISHED_WITH_FAILURE + ); + + $this->assertEquals( + $this->model->operationStatusToBulkSummaryStatus(OperationInterface::STATUS_TYPE_COMPLETE), + BulkSummaryInterface::FINISHED_SUCCESSFULLY + ); + + $this->assertEquals( + $this->model->operationStatusToBulkSummaryStatus(OperationInterface::STATUS_TYPE_OPEN), + BulkSummaryInterface::IN_PROGRESS + ); + + $this->assertEquals( + $this->model->operationStatusToBulkSummaryStatus(0), + BulkSummaryInterface::NOT_STARTED + ); + } + + public function testOperationStatusToBulkSummaryStatusWithUnknownStatus() + { + $this->assertNull($this->model->operationStatusToBulkSummaryStatus('unknown_status')); + } + + public function testBulkSummaryStatusToOperationStatus() + { + $this->assertEquals( + $this->model->bulkSummaryStatusToOperationStatus(BulkSummaryInterface::FINISHED_SUCCESSFULLY), + OperationInterface::STATUS_TYPE_COMPLETE + ); + + $this->assertEquals( + $this->model->bulkSummaryStatusToOperationStatus(BulkSummaryInterface::IN_PROGRESS), + OperationInterface::STATUS_TYPE_OPEN + ); + + $this->assertEquals( + $this->model->bulkSummaryStatusToOperationStatus(BulkSummaryInterface::FINISHED_WITH_FAILURE), + [ + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_REJECTED + ] + ); + + $this->assertEquals( + $this->model->bulkSummaryStatusToOperationStatus(BulkSummaryInterface::NOT_STARTED), + 0 + ); + } + + public function testBulkSummaryStatusToOperationStatusWithUnknownStatus() + { + $this->assertNull($this->model->bulkSummaryStatusToOperationStatus('unknown_status')); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/AdminNotification/PluginTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/AdminNotification/PluginTest.php new file mode 100644 index 0000000000000..cc0b3a3da38a7 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/AdminNotification/PluginTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Test\Unit\Ui\Component\AdminNotification; + +use Magento\Framework\AuthorizationInterface; + +class PluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\AsynchronousOperations\Ui\Component\AdminNotification\Plugin + */ + private $plugin; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $authorizationMock; + + protected function setUp() + { + $this->authorizationMock = $this->createMock(AuthorizationInterface::class); + $this->plugin = new \Magento\AsynchronousOperations\Ui\Component\AdminNotification\Plugin( + $this->authorizationMock + ); + } + + public function testAfterGetMeta() + { + $result = []; + $expectedResult = [ + 'columns' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'isAllowed' => true + ] + ] + ] + ] + ]; + $dataProviderMock = $this->createMock(\Magento\AdminNotification\Ui\Component\DataProvider\DataProvider::class); + $this->authorizationMock->expects($this->once())->method('isAllowed')->willReturn(true); + $this->assertEquals($expectedResult, $this->plugin->afterGetMeta($dataProviderMock, $result)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php new file mode 100644 index 0000000000000..f5cce7af943a1 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Ui\Component\Listing\Column; + +use Magento\AsynchronousOperations\Model\BulkSummary; + +class ActionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $uiComponentFactory; + + /** + * @var \Magento\AsynchronousOperations\Ui\Component\Listing\Column\Actions + */ + private $actionColumn; + + /** + * Set up + */ + protected function setUp() + { + $this->context = $this->createMock(\Magento\Framework\View\Element\UiComponent\ContextInterface::class); + $this->uiComponentFactory = $this->createMock(\Magento\Framework\View\Element\UiComponentFactory::class); + $processor = $this->createPartialMock( + \Magento\Framework\View\Element\UiComponent\Processor::class, + ['getProcessor'] + ); + $this->context->expects($this->never())->method('getProcessor')->will($this->returnValue($processor)); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->actionColumn = $objectManager->getObject( + \Magento\AsynchronousOperations\Ui\Component\Listing\Column\Actions::class, + [ + 'context' => $this->context, + 'uiComponentFactory' => $this->uiComponentFactory, + 'components' => [], + 'data' => ['name' => 'Edit'], + 'editUrl' => '' + ] + ); + } + + /** + * Test for method prepareDataSource + */ + public function testPrepareDataSource() + { + $href = 'bulk/bulk/details/id/bulk-1'; + $this->context->expects($this->once())->method('getUrl')->with( + 'bulk/bulk/details', + ['uuid' => 'bulk-1'] + )->willReturn($href); + $dataSource['data']['items']['item'] = [BulkSummary::BULK_ID => 'bulk-1']; + $actionColumn['data']['items']['item'] = [ + 'Edit' => [ + 'edit' => [ + 'href' => $href, + 'label' => __('Details'), + 'hidden' => false + ] + ] + ]; + $expectedResult = array_merge_recursive($dataSource, $actionColumn); + $this->assertEquals($expectedResult, $this->actionColumn->prepareDataSource($dataSource)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationActionsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationActionsTest.php new file mode 100644 index 0000000000000..a35fd82774148 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationActionsTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Ui\Component\Listing\Column; + +use Magento\AsynchronousOperations\Model\BulkSummary; +use Magento\Framework\Bulk\BulkSummaryInterface; + +class NotificationActionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $uiComponentFactory; + + /** + * @var \Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationActions + */ + private $actionColumn; + + /** + * Set up + */ + protected function setUp() + { + $this->context = $this->createMock(\Magento\Framework\View\Element\UiComponent\ContextInterface::class); + $this->uiComponentFactory = $this->createMock(\Magento\Framework\View\Element\UiComponentFactory::class); + $processor = $this->createPartialMock( + \Magento\Framework\View\Element\UiComponent\Processor::class, + ['getProcessor'] + ); + $this->context->expects($this->never())->method('getProcessor')->will($this->returnValue($processor)); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->actionColumn = $objectManager->getObject( + \Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationActions::class, + [ + 'context' => $this->context, + 'uiComponentFactory' => $this->uiComponentFactory, + 'components' => [], + 'data' => ['name' => 'actions'] + ] + ); + } + + public function testPrepareDataSource() + { + $testData['data']['items'] = [ + [ + 'key' => 'value', + ], + [ + BulkSummary::BULK_ID => 'uuid-1', + 'status' => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + ], + [ + BulkSummary::BULK_ID => 'uuid-2', + ], + ]; + $expectedResult['data']['items'] = [ + [ + 'key' => 'value', + ], + [ + BulkSummary::BULK_ID => 'uuid-1', + 'status' => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + 'actions' => [ + 'details' => [ + 'href' => '#', + 'label' => __('View Details'), + 'callback' => [ + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'destroyInserted', + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'updateData', + 'params' => [ + BulkSummary::BULK_ID => 'uuid-1', + ], + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal', + 'target' => 'openModal', + ], + [ + 'provider' => 'ns = notification_area, index = columns', + 'target' => 'dismiss', + 'params' => ['uuid-1'], + ], + ], + ], + ], + ], + [ + BulkSummary::BULK_ID => 'uuid-2', + 'actions' => [ + 'details' => [ + 'href' => '#', + 'label' => __('View Details'), + 'callback' => [ + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'destroyInserted', + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'updateData', + 'params' => [ + BulkSummary::BULK_ID => 'uuid-2', + ], + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal', + 'target' => 'openModal', + ], + ], + ], + ], + ], + ]; + $this->assertEquals($expectedResult, $this->actionColumn->prepareDataSource($testData)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationDismissActionsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationDismissActionsTest.php new file mode 100644 index 0000000000000..cf1f0db58dfdf --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Listing/Column/NotificationDismissActionsTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Ui\Component\Listing\Column; + +use Magento\AsynchronousOperations\Model\BulkSummary; +use Magento\Framework\Bulk\BulkSummaryInterface; + +class NotificationDismissActionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $uiComponentFactory; + + /** + * @var \Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationDismissActions + */ + private $actionColumn; + + /** + * Set up + */ + protected function setUp() + { + $this->context = $this->createMock(\Magento\Framework\View\Element\UiComponent\ContextInterface::class); + $this->uiComponentFactory = $this->createMock(\Magento\Framework\View\Element\UiComponentFactory::class); + $processor = $this->createPartialMock( + \Magento\Framework\View\Element\UiComponent\Processor::class, + ['getProcessor'] + ); + $this->context->expects($this->never())->method('getProcessor')->will($this->returnValue($processor)); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->actionColumn = $objectManager->getObject( + \Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationDismissActions::class, + [ + 'context' => $this->context, + 'uiComponentFactory' => $this->uiComponentFactory, + 'components' => [], + 'data' => ['name' => 'actions'] + ] + ); + } + + public function testPrepareDataSource() + { + $testData['data']['items'] = [ + [ + 'key' => 'value', + ], + [ + BulkSummary::BULK_ID => 'uuid-1', + 'status' => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + ], + [ + 'status' => BulkSummaryInterface::IN_PROGRESS, + ], + ]; + $expectedResult['data']['items'] = [ + [ + 'key' => 'value', + ], + [ + BulkSummary::BULK_ID => 'uuid-1', + 'status' => BulkSummaryInterface::FINISHED_SUCCESSFULLY, + 'actions' => [ + 'dismiss' => [ + 'href' => '#', + 'label' => __('Dismiss'), + 'callback' => [ + [ + 'provider' => 'ns = notification_area, index = columns', + 'target' => 'dismiss', + 'params' => [ + 0 => 'uuid-1', + ], + ], + ], + ], + ], + ], + [ + 'status' => BulkSummaryInterface::IN_PROGRESS, + ], + ]; + $this->assertEquals($expectedResult, $this->actionColumn->prepareDataSource($testData)); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Operation/DataProviderTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Operation/DataProviderTest.php new file mode 100644 index 0000000000000..bc1e4bcd7e3e2 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Ui/Component/Operation/DataProviderTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Test\Unit\Ui\Component\Operation; + +use Magento\AsynchronousOperations\Ui\Component\Operation\DataProvider; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class DataProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DataProvider + */ + private $dataProvider; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkCollectionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkCollectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $operationDetailsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkMock; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $helper = new ObjectManager($this); + + $this->bulkCollectionFactoryMock = $this->createPartialMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory::class, + ['create'] + ); + $this->bulkCollectionMock = $this->createMock( + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection::class + ); + $this->operationDetailsMock = $this->createMock(\Magento\AsynchronousOperations\Model\Operation\Details::class); + $this->bulkMock = $this->createMock(\Magento\AsynchronousOperations\Model\BulkSummary::class); + $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + + $this->bulkCollectionFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->bulkCollectionMock); + + $this->dataProvider = $helper->getObject( + \Magento\AsynchronousOperations\Ui\Component\Operation\DataProvider::class, + [ + 'name' => 'test-name', + 'bulkCollectionFactory' => $this->bulkCollectionFactoryMock, + 'operationDetails' => $this->operationDetailsMock, + 'request' => $this->requestMock + ] + ); + } + + public function testGetData() + { + $testData = [ + 'id' => '1', + 'uuid' => 'bulk-uuid1', + 'user_id' => '2', + 'description' => 'Description' + ]; + $testOperationData = [ + 'operations_total' => 2, + 'operations_successful' => 1, + 'operations_failed' => 2 + ]; + $testSummaryData = [ + 'summary' => '2 items selected for mass update, 1 successfully updated, 2 failed to update' + ]; + $resultData[$testData['id']] = array_merge($testData, $testOperationData, $testSummaryData); + + $this->bulkCollectionMock + ->expects($this->once()) + ->method('getItems') + ->willReturn([$this->bulkMock]); + $this->bulkMock + ->expects($this->once()) + ->method('getData') + ->willReturn($testData); + $this->operationDetailsMock + ->expects($this->once()) + ->method('getDetails') + ->with($testData['uuid']) + ->willReturn($testOperationData); + $this->bulkMock + ->expects($this->once()) + ->method('getBulkId') + ->willReturn($testData['id']); + + $expectedResult = $this->dataProvider->getData(); + $this->assertEquals($resultData, $expectedResult); + } + + public function testPrepareMeta() + { + $resultData['retriable_operations']['arguments']['data']['disabled'] = true; + $resultData['failed_operations']['arguments']['data']['disabled'] = true; + $testData = [ + 'uuid' => 'bulk-uuid1', + 'failed_retriable' => 0, + 'failed_not_retriable' => 0 + ]; + + $this->requestMock + ->expects($this->once()) + ->method('getParam') + ->willReturn($testData['uuid']); + $this->operationDetailsMock + ->expects($this->once()) + ->method('getDetails') + ->with($testData['uuid']) + ->willReturn($testData); + + $expectedResult = $this->dataProvider->prepareMeta([]); + $this->assertEquals($resultData, $expectedResult); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/AdminNotification/Plugin.php b/app/code/Magento/AsynchronousOperations/Ui/Component/AdminNotification/Plugin.php new file mode 100644 index 0000000000000..b5670639dce09 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/AdminNotification/Plugin.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Ui\Component\AdminNotification; + +/** + * Class Plugin to eliminate Bulk related links in the notification area + */ +class Plugin +{ + /** + * @var \Magento\Framework\AuthorizationInterface + */ + private $authorization; + + /** + * @var bool + */ + private $isAllowed; + + /** + * Plugin constructor. + * @param \Magento\Framework\AuthorizationInterface $authorization + */ + public function __construct( + \Magento\Framework\AuthorizationInterface $authorization + ) { + $this->authorization = $authorization; + } + + /** + * Prepares Meta + * + * @param \Magento\AdminNotification\Ui\Component\DataProvider\DataProvider $dataProvider + * @param array $result + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetMeta( + \Magento\AdminNotification\Ui\Component\DataProvider\DataProvider $dataProvider, + $result + ) { + if (!isset($this->isAllowed)) { + $this->isAllowed = $this->authorization->isAllowed( + 'Magento_Logging::system_magento_logging_bulk_operations' + ); + } + $result['columns']['arguments']['data']['config']['isAllowed'] = $this->isAllowed; + return $result; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Bulk/IdentifierResolver.php b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Bulk/IdentifierResolver.php new file mode 100644 index 0000000000000..b5b7da1318001 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Bulk/IdentifierResolver.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\DataProvider\Bulk; + +use Magento\Framework\App\RequestInterface; + +/** + * Class IdentifierResolver + */ +class IdentifierResolver +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @param RequestInterface $request + */ + public function __construct( + RequestInterface $request + ) { + $this->request = $request; + } + + /** + * @return null|string + */ + public function execute() + { + return $this->request->getParam('uuid'); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Failed/SearchResult.php b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Failed/SearchResult.php new file mode 100644 index 0000000000000..aba7554c26d1d --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Failed/SearchResult.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Failed; + +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy; +use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory; +use Magento\Framework\Event\ManagerInterface as EventManager; +use Psr\Log\LoggerInterface as Logger; +use Magento\AsynchronousOperations\Ui\Component\DataProvider\Bulk\IdentifierResolver; +use Magento\Framework\Bulk\OperationInterface; + +/** + * Class SearchResult + */ +class SearchResult extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult +{ + /** + * @var IdentifierResolver + */ + private $identifierResolver; + + /** + * @var \Magento\Framework\Json\Helper\Data + */ + private $jsonHelper; + + /** + * SearchResult constructor. + * @param EntityFactory $entityFactory + * @param Logger $logger + * @param FetchStrategy $fetchStrategy + * @param EventManager $eventManager + * @param IdentifierResolver $identifierResolver + * @param \Magento\Framework\Json\Helper\Data $jsonHelper + * @param string $mainTable + * @param null $resourceModel + * @param string $identifierName identifier field name for collection items + */ + public function __construct( + EntityFactory $entityFactory, + Logger $logger, + FetchStrategy $fetchStrategy, + EventManager $eventManager, + IdentifierResolver $identifierResolver, + \Magento\Framework\Json\Helper\Data $jsonHelper, + $mainTable = 'magento_operation', + $resourceModel = null, + $identifierName = 'id' + ) { + $this->jsonHelper = $jsonHelper; + $this->identifierResolver = $identifierResolver; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $mainTable, + $resourceModel, + $identifierName + ); + } + + /** + * {@inheritdoc} + */ + protected function _initSelect() + { + $bulkUuid = $this->identifierResolver->execute(); + $this->getSelect()->from(['main_table' => $this->getMainTable()], ['id', 'result_message', 'serialized_data']) + ->where('bulk_uuid=?', $bulkUuid) + ->where('status=?', OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function _afterLoad() + { + parent::_afterLoad(); + foreach ($this->_items as $key => $item) { + try { + $unserializedData = $this->jsonHelper->jsonDecode($item['serialized_data']); + } catch (\Exception $e) { + $this->_logger->error($e->getMessage()); + $unserializedData = []; + } + $this->_items[$key]->setData('meta_information', $this->provideMetaInfo($unserializedData)); + $this->_items[$key]->setData('link', $this->getLink($unserializedData)); + $this->_items[$key]->setData('entity_id', $this->getEntityId($unserializedData)); + } + return $this; + } + + /** + * Provide meta info by serialized data + * + * @param array $item + * @return string + */ + private function provideMetaInfo($item) + { + $metaInfo = ''; + if (isset($item['meta_information'])) { + $metaInfo = $item['meta_information']; + } + return $metaInfo; + } + + /** + * Get link from serialized data + * + * @param array $item + * @return string + */ + private function getLink($item) + { + $entityLink = ''; + if (isset($item['entity_link'])) { + $entityLink = $item['entity_link']; + } + return $entityLink; + } + + /** + * Get entity id from serialized data + * + * @param array $item + * @return string + */ + private function getEntityId($item) + { + $entityLink = ''; + if (isset($item['entity_id'])) { + $entityLink = $item['entity_id']; + } + return $entityLink; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Retriable/SearchResult.php b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Retriable/SearchResult.php new file mode 100644 index 0000000000000..9641bd1333f9f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/Operation/Retriable/SearchResult.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Retriable; + +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy; +use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory; +use Magento\Framework\Event\ManagerInterface as EventManager; +use Psr\Log\LoggerInterface as Logger; +use Magento\AsynchronousOperations\Ui\Component\DataProvider\Bulk\IdentifierResolver; +use Magento\Framework\Bulk\OperationInterface; + +/** + * Class SearchResult + */ +class SearchResult extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult +{ + /** + * @var IdentifierResolver + */ + private $identifierResolver; + + /** + * SearchResult constructor. + * @param EntityFactory $entityFactory + * @param Logger $logger + * @param FetchStrategy $fetchStrategy + * @param EventManager $eventManager + * @param IdentifierResolver $identifierResolver + * @param string $mainTable + * @param null $resourceModel + * @param string $identifierName + */ + public function __construct( + EntityFactory $entityFactory, + Logger $logger, + FetchStrategy $fetchStrategy, + EventManager $eventManager, + IdentifierResolver $identifierResolver, + $mainTable = 'magento_operation', + $resourceModel = null, + $identifierName = 'id' + ) { + $this->identifierResolver = $identifierResolver; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $mainTable, + $resourceModel, + $identifierName + ); + } + + /** + * {@inheritdoc} + */ + protected function _initSelect() + { + $bulkUuid = $this->identifierResolver->execute(); + $this->getSelect()->from(['main_table' => $this->getMainTable()], ['id', 'result_message', 'error_code']) + ->where('bulk_uuid=?', $bulkUuid) + ->where('status=?', OperationInterface::STATUS_TYPE_RETRIABLY_FAILED) + ->group('error_code') + ->columns(['records_qty' => new \Zend_Db_Expr('COUNT(id)')]); + return $this; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/SearchResult.php b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/SearchResult.php new file mode 100644 index 0000000000000..5f2fbd9ea8b11 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/DataProvider/SearchResult.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\DataProvider; + +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy; +use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory; +use Magento\Framework\Event\ManagerInterface as EventManager; +use Psr\Log\LoggerInterface as Logger; +use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\Bulk\BulkSummaryInterface; +use Magento\AsynchronousOperations\Model\StatusMapper; +use Magento\AsynchronousOperations\Model\BulkStatus\CalculatedStatusSql; + +/** + * Class SearchResult + */ +class SearchResult extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult +{ + /** + * @var UserContextInterface + */ + private $userContext; + + /** + * @var StatusMapper + */ + private $statusMapper; + + /** + * @var array|int + */ + private $operationStatus; + + /** + * @var CalculatedStatusSql + */ + private $calculatedStatusSql; + + /** + * SearchResult constructor. + * @param EntityFactory $entityFactory + * @param Logger $logger + * @param FetchStrategy $fetchStrategy + * @param EventManager $eventManager + * @param UserContextInterface $userContextInterface + * @param StatusMapper $statusMapper + * @param CalculatedStatusSql $calculatedStatusSql + * @param string $mainTable + * @param null $resourceModel + * @param string $identifierName + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + EntityFactory $entityFactory, + Logger $logger, + FetchStrategy $fetchStrategy, + EventManager $eventManager, + UserContextInterface $userContextInterface, + StatusMapper $statusMapper, + CalculatedStatusSql $calculatedStatusSql, + $mainTable = 'magento_bulk', + $resourceModel = null, + $identifierName = 'uuid' + ) { + $this->userContext = $userContextInterface; + $this->statusMapper = $statusMapper; + $this->calculatedStatusSql = $calculatedStatusSql; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $mainTable, + $resourceModel, + $identifierName + ); + } + + /** + * {@inheritdoc} + */ + protected function _initSelect() + { + $this->getSelect()->from( + ['main_table' => $this->getMainTable()], + [ + '*', + 'status' => $this->calculatedStatusSql->get($this->getTable('magento_operation')) + ] + )->where( + 'user_id=?', + $this->userContext->getUserId() + ); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function _afterLoad() + { + /** @var BulkSummaryInterface $item */ + foreach ($this->getItems() as $item) { + $item->setStatus($this->statusMapper->operationStatusToBulkSummaryStatus($item->getStatus())); + } + return parent::_afterLoad(); + } + + /** + * {@inheritdoc} + */ + public function addFieldToFilter($field, $condition = null) + { + if ($field == 'status') { + if (is_array($condition)) { + foreach ($condition as $value) { + $this->operationStatus = $this->statusMapper->bulkSummaryStatusToOperationStatus($value); + if (is_array($this->operationStatus)) { + foreach ($this->operationStatus as $statusValue) { + $this->getSelect()->orHaving('status = ?', $statusValue); + } + continue; + } + $this->getSelect()->having('status = ?', $this->operationStatus); + } + } + return $this; + } + return parent::addFieldToFilter($field, $condition); + } + + /** + * {@inheritdoc} + */ + public function getSelectCountSql() + { + $select = parent::getSelectCountSql(); + $select->columns(['status' => $this->calculatedStatusSql->get($this->getTable('magento_operation'))]); + //add grouping by status if filtering by status was executed + if (isset($this->operationStatus)) { + $select->group('status'); + } + return $select; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/Actions.php b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/Actions.php new file mode 100644 index 0000000000000..232f8ca1356be --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/Actions.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Ui\Component\Listing\Column; + +use Magento\Ui\Component\Listing\Columns\Column; + +/** + * Class Actions + */ +class Actions extends Column +{ + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + $dataSource = parent::prepareDataSource($dataSource); + + if (empty($dataSource['data']['items'])) { + return $dataSource; + } + + foreach ($dataSource['data']['items'] as &$item) { + $item[$this->getData('name')]['edit'] = [ + 'href' => $this->context->getUrl( + 'bulk/bulk/details', + ['uuid' => $item['uuid']] + ), + 'label' => __('Details'), + 'hidden' => false, + ]; + } + + return $dataSource; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationActions.php b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationActions.php new file mode 100644 index 0000000000000..1886bbf430bc7 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationActions.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\Listing\Column; + +use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\Bulk\BulkSummaryInterface; + +/** + * Class Actions + */ +class NotificationActions extends Column +{ + /** + * {@inheritdoc} + */ + public function prepareDataSource(array $dataSource) + { + $dataSource = parent::prepareDataSource($dataSource); + + if (empty($dataSource['data']['items'])) { + return $dataSource; + } + + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item['uuid'])) { + $item[$this->getData('name')]['details'] = [ + 'callback' => [ + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'destroyInserted', + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal.insertBulk', + 'target' => 'updateData', + 'params' => [ + 'uuid' => $item['uuid'], + ], + ], + [ + 'provider' => 'notification_area.notification_area.modalContainer.modal', + 'target' => 'openModal', + ], + ], + 'href' => '#', + 'label' => __('View Details'), + ]; + + if (isset($item['status']) + && ($item['status'] === BulkSummaryInterface::FINISHED_SUCCESSFULLY + || $item['status'] === BulkSummaryInterface::FINISHED_WITH_FAILURE) + ) { + $item[$this->getData('name')]['details']['callback'][] = [ + 'provider' => 'ns = notification_area, index = columns', + 'target' => 'dismiss', + 'params' => [ + 0 => $item['uuid'], + ], + ]; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationDismissActions.php b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationDismissActions.php new file mode 100644 index 0000000000000..cae2524f92600 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/Listing/Column/NotificationDismissActions.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AsynchronousOperations\Ui\Component\Listing\Column; + +use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\Bulk\BulkSummaryInterface; + +/** + * Class NotificationDismissActions + */ +class NotificationDismissActions extends Column +{ + /** + * {@inheritdoc} + */ + public function prepareDataSource(array $dataSource) + { + $dataSource = parent::prepareDataSource($dataSource); + + if (empty($dataSource['data']['items'])) { + return $dataSource; + } + + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item['status']) + && ($item['status'] === BulkSummaryInterface::FINISHED_SUCCESSFULLY + || $item['status'] === BulkSummaryInterface::FINISHED_WITH_FAILURE) + ) { + $item[$this->getData('name')]['dismiss'] = [ + 'callback' => [ + [ + 'provider' => 'ns = notification_area, index = columns', + 'target' => 'dismiss', + 'params' => [ + 0 => $item['uuid'], + ], + ], + ], + 'href' => '#', + 'label' => __('Dismiss'), + ]; + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Ui/Component/Operation/DataProvider.php b/app/code/Magento/AsynchronousOperations/Ui/Component/Operation/DataProvider.php new file mode 100644 index 0000000000000..89aae531fec4e --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Ui/Component/Operation/DataProvider.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AsynchronousOperations\Ui\Component\Operation; + +/** + * Class DataProvider + */ +class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * @var \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\Collection + */ + protected $collection; + + /** + * @var \Magento\AsynchronousOperations\Model\Operation\Details + */ + private $operationDetails; + + /** + * @var \Magento\Framework\App\RequestInterface $request, + */ + private $request; + + /** + * DataProvider constructor. + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory $bulkCollectionFactory + * @param \Magento\AsynchronousOperations\Model\Operation\Details $operationDetails + * @param \Magento\Framework\App\RequestInterface $request + * @param array $meta + * @param array $data + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + \Magento\AsynchronousOperations\Model\ResourceModel\Bulk\CollectionFactory $bulkCollectionFactory, + \Magento\AsynchronousOperations\Model\Operation\Details $operationDetails, + \Magento\Framework\App\RequestInterface $request, + array $meta = [], + array $data = [] + ) { + $this->collection = $bulkCollectionFactory->create(); + $this->operationDetails = $operationDetails; + $this->request = $request; + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + $this->meta = $this->prepareMeta($this->meta); + } + + /** + * Human readable summary for bulk + * + * @param array $operationDetails structure is implied as getOperationDetails() result + * @return string + */ + private function getSummaryReport($operationDetails) + { + if (0 == $operationDetails['operations_successful'] && 0 == $operationDetails['operations_failed']) { + return __('Pending, in queue...'); + } + + $summaryReport = __('%1 items selected for mass update', $operationDetails['operations_total'])->__toString(); + if ($operationDetails['operations_successful'] > 0) { + $summaryReport .= __(', %1 successfully updated', $operationDetails['operations_successful']); + } + + if ($operationDetails['operations_failed'] > 0) { + $summaryReport .= __(', %1 failed to update', $operationDetails['operations_failed']); + } + + return $summaryReport; + } + + /** + * Bulk summary with operation statistics + * + * @return array + */ + public function getData() + { + $data = []; + $items = $this->collection->getItems(); + if (count($items) == 0) { + return $data; + } + $bulk = array_shift($items); + /** @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface $bulk */ + $data = $bulk->getData(); + $operationDetails = $this->operationDetails->getDetails($data['uuid']); + $data['summary'] = $this->getSummaryReport($operationDetails); + $data = array_merge($data, $operationDetails); + + return [$bulk->getBulkId() => $data]; + } + + /** + * Prepares Meta + * + * @param array $meta + * @return array + */ + public function prepareMeta($meta) + { + $requestId = $this->request->getParam($this->requestFieldName); + $operationDetails = $this->operationDetails->getDetails($requestId); + + if (isset($operationDetails['failed_retriable']) && !$operationDetails['failed_retriable']) { + $meta['retriable_operations']['arguments']['data']['disabled'] = true; + } + + if (isset($operationDetails['failed_not_retriable']) && !$operationDetails['failed_not_retriable']) { + $meta['failed_operations']['arguments']['data']['disabled'] = true; + } + + return $meta; + } +} diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json new file mode 100644 index 0000000000000..18927b5f4ecca --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -0,0 +1,32 @@ +{ + "name": "magento/module-asynchronous-operations", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "magento/framework": "*", + "magento/framework-bulk": "*", + "magento/module-authorization": "*", + "magento/module-backend": "*", + "magento/module-ui": "*", + "php": "~7.1.3||~7.2.0" + }, + "suggest": { + "magento/module-admin-notification": "*", + "magento/module-logging": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AsynchronousOperations\\": "" + } + } +} diff --git a/app/code/Magento/AsynchronousOperations/etc/acl.xml b/app/code/Magento/AsynchronousOperations/etc/acl.xml new file mode 100644 index 0000000000000..42521ad40ff63 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/acl.xml @@ -0,0 +1,21 @@ +<?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:Acl/etc/acl.xsd"> + <acl> + <resources> + <resource id="Magento_Backend::admin"> + <resource id="Magento_Backend::system"> + <resource id="Magento_Logging::magento_logging" title="Action Log" translate="title" sortOrder="70"> + <resource id="Magento_Logging::magento_logging_events" title="Report" translate="title" sortOrder="10"/> + <resource id="Magento_Logging::system_magento_logging_bulk_operations" title="Bulk Actions" translate="title" sortOrder="15"/> + </resource> + </resource> + </resource> + </resources> + </acl> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/adminhtml/di.xml b/app/code/Magento/AsynchronousOperations/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..26dd6a39473a6 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/adminhtml/di.xml @@ -0,0 +1,15 @@ +<?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:ObjectManager/etc/config.xsd"> + <type name="Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized"> + <plugin name="afterToArray" type="Magento\AsynchronousOperations\Model\ResourceModel\System\Message\Collection\Synchronized\Plugin" /> + </type> + <type name="Magento\AdminNotification\Ui\Component\DataProvider\DataProvider"> + <plugin name="afterGetMeta" type="Magento\AsynchronousOperations\Ui\Component\AdminNotification\Plugin" /> + </type> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/adminhtml/menu.xml b/app/code/Magento/AsynchronousOperations/etc/adminhtml/menu.xml new file mode 100644 index 0000000000000..2e9fe34c45cec --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/adminhtml/menu.xml @@ -0,0 +1,26 @@ +<?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_Backend:etc/menu.xsd"> + <menu> + <add id="Magento_AsynchronousOperations::system_magento_logging" + title="Action Logs" + translate="title" + module="Magento_AsynchronousOperations" + sortOrder="70" parent="Magento_Backend::system" + dependsOnModule="Magento_AsynchronousOperations" + resource="Magento_Logging::magento_logging"/> + <add id="Magento_AsynchronousOperations::system_magento_logging_bulk_operations" + title="Bulk Actions" + translate="title" + module="Magento_AsynchronousOperations" + sortOrder="25" + parent="Magento_AsynchronousOperations::system_magento_logging" + action="bulk/index/" + resource="Magento_Logging::system_magento_logging_bulk_operations"/> + </menu> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/adminhtml/routes.xml b/app/code/Magento/AsynchronousOperations/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..a255af90eac8a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?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:App/etc/routes.xsd"> + <router id="admin"> + <route id="bulk" frontName="bulk"> + <module name="Magento_AsynchronousOperations" before="Magento_Backend" /> + </route> + </router> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml b/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..7190b80750357 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml @@ -0,0 +1,20 @@ +<?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_Config:etc/system_file.xsd"> + <system> + <section id="system"> + <tab>advanced</tab> + <group id="bulk" translate="label" showInDefault="1" showInWebsite="0" showInStore="0" sortOrder="600"> + <label>Bulk Actions</label> + <field id="lifetime" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Days Saved in Log</label> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/config.xml b/app/code/Magento/AsynchronousOperations/etc/config.xml new file mode 100644 index 0000000000000..e30c1005d0dd0 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/config.xml @@ -0,0 +1,16 @@ +<?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_Store:etc/config.xsd"> + <default> + <system> + <bulk> + <lifetime>60</lifetime> + </bulk> + </system> + </default> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/crontab.xml b/app/code/Magento/AsynchronousOperations/etc/crontab.xml new file mode 100644 index 0000000000000..c55b0a886ac79 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/crontab.xml @@ -0,0 +1,14 @@ +<?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_Cron:etc/crontab.xsd"> + <group id="default"> + <job name="bulk_cleanup" instance="Magento\AsynchronousOperations\Cron\BulkCleanup" method="execute"> + <schedule>* * * * *</schedule> + </job> + </group> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml new file mode 100644 index 0000000000000..342326e6666f1 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="magento_bulk" resource="default" engine="innodb" + comment="Bulk entity that represents set of related asynchronous operations"> + <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Bulk Internal ID (must not be exposed)"/> + <column xsi:type="varbinary" name="uuid" nullable="true" length="39" + comment="Bulk UUID (can be exposed to reference bulk entity)"/> + <column xsi:type="int" name="user_id" padding="10" unsigned="true" nullable="true" identity="false" + comment="ID of the WebAPI user that performed an action"/> + <column xsi:type="int" name="user_type" nullable="true" comment="Which type of user"/> + <column xsi:type="varchar" name="description" nullable="true" length="255" comment="Bulk Description"/> + <column xsi:type="int" name="operation_count" padding="10" unsigned="true" nullable="false" identity="false" + comment="Total number of operations scheduled within this bulk"/> + <column xsi:type="timestamp" name="start_time" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" + comment="Bulk start time"/> + <constraint xsi:type="primary" name="PRIMARY"> + <column name="id"/> + </constraint> + <constraint xsi:type="unique" name="MAGENTO_BULK_UUID"> + <column name="uuid"/> + </constraint> + <index name="MAGENTO_BULK_USER_ID_ADMIN_USER_USER_ID" indexType="btree"> + <column name="user_id"/> + </index> + </table> + <table name="magento_operation" resource="default" engine="innodb" comment="Operation entity"> + <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Operation ID"/> + <column xsi:type="varbinary" name="bulk_uuid" nullable="true" length="39" comment="Related Bulk UUID"/> + <column xsi:type="varchar" name="topic_name" nullable="true" length="255" + comment="Name of the related message queue topic"/> + <column xsi:type="blob" name="serialized_data" nullable="true" + comment="Data (serialized) required to perform an operation"/> + <column xsi:type="blob" name="result_serialized_data" nullable="true" + comment="Result data (serialized) after perform an operation"/> + <column xsi:type="smallint" name="status" padding="6" unsigned="false" nullable="true" identity="false" + default="0" comment="Operation status (OPEN | COMPLETE | RETRIABLY_FAILED | NOT_RETRIABLY_FAILED)"/> + <column xsi:type="smallint" name="error_code" padding="6" unsigned="false" nullable="true" identity="false" + comment="Code of the error that appeared during operation execution (used to aggregate related failed operations)"/> + <column xsi:type="varchar" name="result_message" nullable="true" length="255" + comment="Operation result message"/> + <constraint xsi:type="primary" name="PRIMARY"> + <column name="id"/> + </constraint> + <constraint xsi:type="foreign" name="MAGENTO_OPERATION_BULK_UUID_MAGENTO_BULK_UUID" table="magento_operation" + column="bulk_uuid" referenceTable="magento_bulk" referenceColumn="uuid" onDelete="CASCADE"/> + <index name="MAGENTO_OPERATION_BULK_UUID_ERROR_CODE" indexType="btree"> + <column name="bulk_uuid"/> + <column name="error_code"/> + </index> + </table> + <table name="magento_acknowledged_bulk" resource="default" engine="innodb" + comment="Bulk that was viewed by user from notification area"> + <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Internal ID"/> + <column xsi:type="varbinary" name="bulk_uuid" nullable="true" length="39" comment="Related Bulk UUID"/> + <constraint xsi:type="primary" name="PRIMARY"> + <column name="id"/> + </constraint> + <constraint xsi:type="foreign" name="MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID_MAGENTO_BULK_UUID" + table="magento_acknowledged_bulk" column="bulk_uuid" referenceTable="magento_bulk" + referenceColumn="uuid" onDelete="CASCADE"/> + <constraint xsi:type="unique" name="MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID"> + <column name="bulk_uuid"/> + </constraint> + </table> +</schema> diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json b/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..423b7553ced2a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json @@ -0,0 +1,51 @@ +{ + "magento_bulk": { + "column": { + "id": true, + "uuid": true, + "user_id": true, + "user_type": true, + "description": true, + "operation_count": true, + "start_time": true + }, + "index": { + "MAGENTO_BULK_USER_ID_ADMIN_USER_USER_ID": true, + "MAGENTO_BULK_USER_ID": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_BULK_UUID": true + } + }, + "magento_operation": { + "column": { + "id": true, + "bulk_uuid": true, + "topic_name": true, + "serialized_data": true, + "result_serialized_data": true, + "status": true, + "error_code": true, + "result_message": true + }, + "index": { + "MAGENTO_OPERATION_BULK_UUID_ERROR_CODE": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_OPERATION_BULK_UUID_MAGENTO_BULK_UUID": true + } + }, + "magento_acknowledged_bulk": { + "column": { + "id": true, + "bulk_uuid": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID_MAGENTO_BULK_UUID": true, + "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID": true + } + } +} \ No newline at end of file diff --git a/app/code/Magento/AsynchronousOperations/etc/di.xml b/app/code/Magento/AsynchronousOperations/etc/di.xml new file mode 100644 index 0000000000000..42b62ff8ea374 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/di.xml @@ -0,0 +1,108 @@ +<?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:ObjectManager/etc/config.xsd"> + <preference for="Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface" type="Magento\AsynchronousOperations\Model\BulkSummary" /> + <preference for="Magento\AsynchronousOperations\Api\Data\OperationInterface" type="Magento\AsynchronousOperations\Model\Operation" /> + <preference for="Magento\AsynchronousOperations\Api\Data\OperationListInterface" type="Magento\AsynchronousOperations\Model\OperationList" /> + <preference for="Magento\Framework\Bulk\BulkManagementInterface" type="Magento\AsynchronousOperations\Model\BulkManagement" /> + <preference for="Magento\AsynchronousOperations\Api\BulkStatusInterface" type="Magento\AsynchronousOperations\Model\BulkOperationsStatus" /> + <preference for="Magento\Framework\Bulk\BulkStatusInterface" type="Magento\AsynchronousOperations\Model\BulkStatus" /> + <preference for="Magento\Framework\Bulk\OperationManagementInterface" type="Magento\AsynchronousOperations\Model\OperationManagement" /> + <preference for="Magento\AsynchronousOperations\Api\Data\SummaryOperationStatusInterface" type="Magento\AsynchronousOperations\Model\OperationStatus" /> + <preference for="Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterface" type="Magento\AsynchronousOperations\Model\BulkStatus\Detailed" /> + <preference for="Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface" type="Magento\AsynchronousOperations\Model\BulkStatus\Short" /> + <preference for="Magento\AsynchronousOperations\Api\Data\OperationSearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> + <preference for="Magento\AsynchronousOperations\Api\OperationRepositoryInterface" type="Magento\AsynchronousOperations\Model\OperationRepository" /> + <type name="Magento\Framework\EntityManager\MetadataPool"> + <arguments> + <argument name="metadata" xsi:type="array"> + <item name="Magento\AsynchronousOperations\Api\Data\OperationInterface" xsi:type="array"> + <item name="entityTableName" xsi:type="string">magento_operation</item> + <item name="identifierField" xsi:type="string">id</item> + </item> + <item name="Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface" xsi:type="array"> + <item name="entityTableName" xsi:type="string">magento_bulk</item> + <item name="identifierField" xsi:type="string">uuid</item> + </item> + <item name="Magento\AsynchronousOperations\Api\Data\OperationListInterface" xsi:type="array"> + <item name="entityTableName" xsi:type="string">magento_operation</item> + <item name="identifierField" xsi:type="string">id</item> + </item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\EntityManager\Mapper"> + <arguments> + <argument name="config" xsi:type="array"> + <item name="Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface" xsi:type="array"> + <item name="uuid" xsi:type="string">bulk_id</item> + </item> + </argument> + </arguments> + </type> + <virtualType name="bulkSummaryMapper" type="Magento\Framework\EntityManager\CompositeMapper"> + <arguments> + <argument name="mappers" xsi:type="array"> + <item name="identifierMapper" xsi:type="object">Magento\AsynchronousOperations\Model\Entity\BulkSummaryMapper</item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Framework\EntityManager\MapperPool"> + <arguments> + <argument name="mappers" xsi:type="array"> + <item name="Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface" xsi:type="string">bulkSummaryMapper</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory"> + <arguments> + <argument name="collections" xsi:type="array"> + <item name="bulk_listing_data_source" xsi:type="string">Magento\AsynchronousOperations\Ui\Component\DataProvider\SearchResult</item> + <item name="failed_operation_listing_data_source" xsi:type="string">Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Failed\SearchResult</item> + <item name="retriable_operation_listing_data_source" xsi:type="string">Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Retriable\SearchResult</item> + <item name="failed_operation_modal_listing_data_source" xsi:type="string">Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Failed\SearchResult</item> + <item name="retriable_operation_modal_listing_data_source" xsi:type="string">Magento\AsynchronousOperations\Ui\Component\DataProvider\Operation\Retriable\SearchResult</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\EntityManager\OperationPool"> + <arguments> + <argument name="operations" xsi:type="array"> + <item name="Magento\AsynchronousOperations\Api\Data\OperationListInterface" xsi:type="array"> + <item name="checkIfExists" xsi:type="string">Magento\AsynchronousOperations\Model\ResourceModel\Operation\CheckIfExists</item> + <item name="create" xsi:type="string">Magento\AsynchronousOperations\Model\ResourceModel\Operation\Create</item> + </item> + </argument> + </arguments> + </type> + <virtualType + name="Magento\AsynchronousOperations\Ui\Component\DataProvider" + type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider"/> + <virtualType name="Magento\AsynchronousOperations\Model\VirtualType\PublisherPool" type="Magento\Framework\MessageQueue\PublisherPool"> + <arguments> + <argument name="publishers" xsi:type="array"> + <item name="async" xsi:type="array"> + <item name="amqp" xsi:type="object">Magento\AsynchronousOperations\Model\MassPublisher</item> + <item name="db" xsi:type="object">Magento\AsynchronousOperations\Model\MassPublisher</item> + </item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\AsynchronousOperations\Model\VirtualType\BulkManagement" type="Magento\AsynchronousOperations\Model\BulkManagement"> + <arguments> + <argument name="publisher" xsi:type="object">Magento\AsynchronousOperations\Model\VirtualType\PublisherPool</argument> + </arguments> + </virtualType> + <type name="Magento\AsynchronousOperations\Model\MassSchedule"> + <arguments> + <argument name="bulkManagement" xsi:type="object">Magento\AsynchronousOperations\Model\VirtualType\BulkManagement</argument> + </arguments> + </type> + <preference for="Magento\AsynchronousOperations\Api\Data\AsyncResponseInterface" type="Magento\AsynchronousOperations\Model\AsyncResponse" /> + <preference for="Magento\AsynchronousOperations\Api\Data\ItemStatusInterface" type="Magento\AsynchronousOperations\Model\ItemStatus" /> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/extension_attributes.xml b/app/code/Magento/AsynchronousOperations/etc/extension_attributes.xml new file mode 100644 index 0000000000000..6eeda62373f06 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/extension_attributes.xml @@ -0,0 +1,19 @@ +<?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:Api/etc/extension_attributes.xsd"> + <extension_attributes for="Magento\AsynchronousOperations\Api\Data\OperationInterface"> + <attribute code="start_time" type="string"> + <resources> + <resource ref="Magento_Logging::system_magento_logging_bulk_operations"/> + </resources> + <join reference_table="magento_bulk" join_on_field="bulk_uuid" reference_field="uuid"> + <field column="start_time">start_time</field> + </join> + </attribute> + </extension_attributes> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/module.xml b/app/code/Magento/AsynchronousOperations/etc/module.xml new file mode 100644 index 0000000000000..8f7a9e144462b --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/module.xml @@ -0,0 +1,14 @@ +<?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:Module/etc/module.xsd"> + <module name="Magento_AsynchronousOperations" > + <sequence> + <module name="Magento_User"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/AsynchronousOperations/etc/webapi.xml b/app/code/Magento/AsynchronousOperations/etc/webapi.xml new file mode 100644 index 0000000000000..4c10a5756c8d6 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/webapi.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> + + <route url="/V1/bulk/:bulkUuid/detailed-status" method="GET"> + <service class="Magento\AsynchronousOperations\Api\BulkStatusInterface" method="getBulkDetailedStatus"/> + <resources> + <resource ref="Magento_Logging::system_magento_logging_bulk_operations" /> + </resources> + </route> + + <route url="/V1/bulk/:bulkUuid/status" method="GET"> + <service class="Magento\AsynchronousOperations\Api\BulkStatusInterface" method="getBulkShortStatus"/> + <resources> + <resource ref="Magento_Logging::system_magento_logging_bulk_operations" /> + </resources> + </route> + + <route url="/V1/bulk/:bulkUuid/operation-status/:status" method="GET"> + <service class="Magento\AsynchronousOperations\Api\BulkStatusInterface" method="getOperationsCountByBulkIdAndStatus"/> + <resources> + <resource ref="Magento_Logging::system_magento_logging_bulk_operations" /> + </resources> + </route> + + <route url="/V1/bulk" method="GET"> + <service class="Magento\AsynchronousOperations\Api\OperationRepositoryInterface" method="getList"/> + <resources> + <resource ref="Magento_Logging::system_magento_logging_bulk_operations" /> + </resources> + </route> + +</routes> diff --git a/app/code/Magento/AsynchronousOperations/i18n/en_US.csv b/app/code/Magento/AsynchronousOperations/i18n/en_US.csv new file mode 100644 index 0000000000000..44cc0a0ab7754 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/i18n/en_US.csv @@ -0,0 +1,35 @@ +Back,Back +Done,Done +Retry,Retry +"'Action Details - #' .","'Action Details - #' ." +"%1 item(s) have been scheduled for update.""","%1 item(s) have been scheduled for update.""" +"Bulk Actions Log","Bulk Actions Log" +"%1 item(s) are currently being updated.","%1 item(s) are currently being updated." +"Task ""%1"": ","Task ""%1"": " +"%1 item(s) have been scheduled for update.","%1 item(s) have been scheduled for update." +"%1 item(s) have been successfully updated.","%1 item(s) have been successfully updated." +"%1 item(s) failed to update","%1 item(s) failed to update" +Details,Details +"View Details","View Details" +Dismiss,Dismiss +"Pending, in queue...","Pending, in queue..." +"%1 items selected for mass update","%1 items selected for mass update" +", %1 successfully updated",", %1 successfully updated" +", %1 failed to update",", %1 failed to update" +"Something went wrong.","Something went wrong." +"Action Log","Action Log" +"Bulk Actions","Bulk Actions" +"Days Saved in Log","Days Saved in Log" +"Description of Operation","Description of Operation" +Summary,Summary +"Start Time","Start Time" +"Items to Retry","Items to Retry" +"To retry, select the items and click “Retry”.","To retry, select the items and click “Retry”." +"Items That Can’t Be Updated.","Items That Can’t Be Updated." +ID,ID +Status,Status +"Meta Information","Meta Information" +Error,Error +"Dismiss All Completed Tasks","Dismiss All Completed Tasks" +"Action Details - #","Action Details - #" +"Number of Records Affected","Number of Records Affected" diff --git a/app/code/Magento/AsynchronousOperations/registration.php b/app/code/Magento/AsynchronousOperations/registration.php new file mode 100644 index 0000000000000..d384df583fb5a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AsynchronousOperations', __DIR__); diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details.xml new file mode 100644 index 0000000000000..a250eed51c781 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details.xml @@ -0,0 +1,16 @@ +<?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" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <update handle="formkey"/> + <update handle="styles"/> + <body> + <referenceContainer name="content"> + <uiComponent name="bulk_details_form"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details_modal.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details_modal.xml new file mode 100644 index 0000000000000..946cf0a898585 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_bulk_details_modal.xml @@ -0,0 +1,16 @@ +<?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" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <update handle="formkey"/> + <update handle="styles"/> + <body> + <referenceContainer name="content"> + <uiComponent name="bulk_details_form_modal"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_index_index.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_index_index.xml new file mode 100644 index 0000000000000..d8686887bbc59 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/layout/bulk_index_index.xml @@ -0,0 +1,16 @@ +<?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" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <update handle="formkey"/> + <update handle="styles"/> + <body> + <referenceContainer name="content"> + <uiComponent name="bulk_listing"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form.xml new file mode 100644 index 0000000000000..19793ac82ba39 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form.xml @@ -0,0 +1,150 @@ +<?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"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">bulk_details_form.bulk_details_form_data_source</item> + </item> + <item name="template" xsi:type="string">templates/form/collapsible</item> + </argument> + <settings> + <buttons> + <button name="save" class="Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\RetryButton"/> + <button name="back" class="Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\BackButton"/> + </buttons> + <namespace>bulk_details_form</namespace> + <dataScope>data</dataScope> + <deps> + <dep>bulk_details_form.bulk_details_form_data_source</dep> + </deps> + </settings> + <dataSource name="bulk_details_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="bulk/bulk/retry"/> + </settings> + <dataProvider class="Magento\AsynchronousOperations\Ui\Component\Operation\DataProvider" name="bulk_details_form_data_source"> + <settings> + <requestFieldName>uuid</requestFieldName> + <primaryFieldName>uuid</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <fieldset name="general" sortOrder="10"> + <settings> + <label/> + </settings> + <field name="description" formElement="input"> + <settings> + <elementTmpl>ui/form/element/text</elementTmpl> + <dataType>text</dataType> + <label translate="true">Description of Operation</label> + </settings> + </field> + <field name="summary" formElement="input"> + <settings> + <elementTmpl>ui/form/element/text</elementTmpl> + <dataType>text</dataType> + <label translate="true">Summary</label> + </settings> + </field> + <field name="start_time" formElement="date"> + <settings> + <elementTmpl>ui/form/element/textDate</elementTmpl> + <dataType>text</dataType> + <label translate="true">Start Time</label> + </settings> + <formElements> + <date> + <settings> + <options> + <option name="showsTime" xsi:type="boolean">true</option> + <option name="dateFormat" xsi:type="string">MMM d, YYYY</option> + <option name="timeFormat" xsi:type="string">h:mm:ss A</option> + </options> + </settings> + </date> + </formElements> + </field> + </fieldset> + <fieldset name="retriable_operations" sortOrder="20"> + <settings> + <label translate="true">Items to Retry</label> + </settings> + <container name="retriable_operations_description" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="text" xsi:type="string" translate="true">To retry, select the items and click “Retry”.</item> + </item> + </argument> + </container> + <field name="retriable_operation_validation" component="Magento_AsynchronousOperations/js/form/error" template="Magento_AsynchronousOperations/form/field" formElement="input"> + <settings> + <additionalClasses> + <class name="message message-warning">true</class> + </additionalClasses> + <validation> + <rule name="required-entry" xsi:type="array"> + <item name="validate" xsi:type="boolean">true</item> + <item name="message" xsi:type="string">An item needs to be selected. Select and try again.</item> + </rule> + </validation> + <elementTmpl/> + <dataType>text</dataType> + <dataScope>operations_to_retry</dataScope> + </settings> + </field> + <insertListing name="retriable_operation"> + <settings> + <externalProvider>${ $.ns }.retriable_operation_listing_data_source</externalProvider> + <loading>false</loading> + <selectionsProvider>${ $.ns }.${ $.ns }.retriable_operation_listing_columns.ids</selectionsProvider> + <autoRender>true</autoRender> + <dataScope>operations_to_retry</dataScope> + <ns>retriable_operation_listing</ns> + <exports> + <link name="uuid">${ $.externalProvider }:params.uuid</link> + </exports> + <imports> + <link name="uuid">${ $.provider }:data.uuid</link> + </imports> + </settings> + </insertListing> + </fieldset> + <fieldset name="failed_operations" sortOrder="30"> + <settings> + <label translate="true">Items That Can’t Be Updated.</label> + </settings> + <container name="failed_operations_description" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + </container> + <insertListing name="failed_operation"> + <settings> + <externalProvider>failed_operation_listing.failed_operation_listing_data_source</externalProvider> + <loading>false</loading> + <autoRender>true</autoRender> + <ns>failed_operation_listing</ns> + <exports> + <link name="uuid">${ $.externalProvider }:params.uuid</link> + </exports> + <imports> + <link name="uuid">${ $.provider }:data.uuid</link> + </imports> + </settings> + </insertListing> + </fieldset> +</form> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form_modal.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form_modal.xml new file mode 100644 index 0000000000000..a7d4ad03a0adf --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_details_form_modal.xml @@ -0,0 +1,146 @@ +<?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"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">bulk_details_form_modal.bulk_details_form_modal_data_source</item> + </item> + <item name="template" xsi:type="string">templates/form/collapsible</item> + </argument> + <settings> + <buttons> + <button name="save" class="Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\RetryButton"/> + <button name="done" class="Magento\AsynchronousOperations\Block\Adminhtml\Bulk\Details\DoneButton"/> + </buttons> + <namespace>bulk_details_form_modal</namespace> + <ajaxSave>true</ajaxSave> + <ajaxSaveType>simple</ajaxSaveType> + <dataScope>data</dataScope> + <deps> + <dep>bulk_details_form_modal.bulk_details_form_modal_data_source</dep> + </deps> + </settings> + <dataSource name="bulk_details_form_modal_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="bulk/bulk/retry"/> + </settings> + <dataProvider class="Magento\AsynchronousOperations\Ui\Component\Operation\DataProvider" name="bulk_details_form_modal_data_source"> + <settings> + <requestFieldName>uuid</requestFieldName> + <primaryFieldName>uuid</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <fieldset name="general" sortOrder="10"> + <settings> + <label/> + </settings> + <field name="description" formElement="input"> + <settings> + <elementTmpl>ui/form/element/text</elementTmpl> + <dataType>text</dataType> + <label translate="true">Description of Operation</label> + </settings> + </field> + <field name="summary" formElement="input"> + <settings> + <elementTmpl>ui/form/element/text</elementTmpl> + <dataType>text</dataType> + <label translate="true">Summary</label> + </settings> + </field> + <field name="start_time" formElement="date"> + <settings> + <elementTmpl>ui/form/element/textDate</elementTmpl> + <dataType>text</dataType> + <label translate="true">Start Time</label> + </settings> + <formElements> + <date> + <settings> + <options> + <option name="timeFormat" xsi:type="string">h:mm:ss A</option> + <option name="dateFormat" xsi:type="string">MMM d, YYYY</option> + <option name="showsTime" xsi:type="boolean">true</option> + </options> + </settings> + </date> + </formElements> + </field> + </fieldset> + <fieldset name="retriable_operations" sortOrder="20"> + <settings> + <label translate="true">Items to Retry</label> + </settings> + <container name="retriable_operations_description" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="text" xsi:type="string" translate="true">To retry, select the items and click “Retry”.</item> + </item> + </argument> + </container> + <field name="retriable_operation_validation" component="Magento_AsynchronousOperations/js/form/error" template="Magento_AsynchronousOperations/form/field" formElement="input"> + <settings> + <validation> + <rule name="required-entry" xsi:type="boolean">true</rule> + </validation> + <elementTmpl/> + <dataType>text</dataType> + <dataScope>operations_to_retry</dataScope> + </settings> + </field> + <insertListing name="retriable_operation"> + <settings> + <externalProvider>${ $.ns }.retriable_operation_modal_listing_data_source</externalProvider> + <loading>false</loading> + <selectionsProvider>${ $.ns }.${ $.ns }.retriable_operation_modal_listing_columns.ids</selectionsProvider> + <autoRender>true</autoRender> + <dataScope>operations_to_retry</dataScope> + <ns>retriable_operation_modal_listing</ns> + <exports> + <link name="uuid">${ $.externalProvider }:params.uuid</link> + </exports> + <imports> + <link name="uuid">${ $.provider }:data.uuid</link> + </imports> + </settings> + </insertListing> + </fieldset> + <fieldset name="failed_operations" sortOrder="30"> + <settings> + <label translate="true">Items That Can’t Be Updated.</label> + </settings> + <container name="failed_operations_description" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + </container> + <insertListing name="failed_operation"> + <settings> + <externalProvider>failed_operation_modal_listing.failed_operation_modal_listing_data_source</externalProvider> + <loading>false</loading> + <autoRender>true</autoRender> + <ns>failed_operation_modal_listing</ns> + <exports> + <link name="uuid">${ $.externalProvider }:params.uuid</link> + </exports> + <imports> + <link name="uuid">${ $.provider }:data.uuid</link> + </imports> + </settings> + </insertListing> + </fieldset> +</form> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_listing.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_listing.xml new file mode 100644 index 0000000000000..512b08d6a8de2 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/bulk_listing.xml @@ -0,0 +1,99 @@ +<?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">bulk_listing.bulk_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>bulk_columns</spinner> + <deps> + <dep>bulk_listing.bulk_listing_data_source</dep> + </deps> + </settings> + <dataSource name="bulk_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Logging::system_magento_logging_bulk_operations</aclResource> + <dataProvider class="Magento\AsynchronousOperations\Ui\Component\DataProvider" name="bulk_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <bookmark name="bookmarks"/> + <columnsControls name="columns_controls"/> + <exportButton name="export_button"/> + <filters name="listing_filters"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="params" xsi:type="array"> + <item name="filters_modifier" xsi:type="array"/> + </item> + <item name="observers" xsi:type="array"/> + </item> + </argument> + <settings> + <statefull> + <property name="applied" xsi:type="boolean">false</property> + </statefull> + </settings> + </filters> + <paging name="listing_paging"/> + </listingToolbar> + <columns name="bulk_columns"> + <selectionsColumn name="ids" sortOrder="10"> + <settings> + <indexField>uuid</indexField> + <visible>false</visible> + </settings> + </selectionsColumn> + <column name="uuid" sortOrder="20"> + <settings> + <filter>text</filter> + <label translate="true">ID</label> + </settings> + </column> + <column name="start_time" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date" sortOrder="40"> + <settings> + <filter>dateRange</filter> + <dataType>date</dataType> + <label translate="true">Start Time</label> + </settings> + </column> + <column name="description" sortOrder="50"> + <settings> + <filter>select</filter> + <options class="Magento\AsynchronousOperations\Model\BulkDescription\Options"/> + <dataType>select</dataType> + <label translate="true">Description of Operation</label> + </settings> + </column> + <column name="status" component="Magento_Ui/js/grid/columns/select" sortOrder="60"> + <settings> + <filter>select</filter> + <options class="Magento\AsynchronousOperations\Model\BulkStatus\Options"/> + <dataType>select</dataType> + <label translate="true">Status</label> + </settings> + </column> + <actionsColumn name="actions" class="\Magento\AsynchronousOperations\Ui\Component\Listing\Column\Actions"> + <settings> + <indexField>id</indexField> + <label>Action</label> + </settings> + </actionsColumn> + </columns> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_listing.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_listing.xml new file mode 100644 index 0000000000000..2ac762e398521 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_listing.xml @@ -0,0 +1,75 @@ +<?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">failed_operation_listing.failed_operation_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>failed_operation_listing_columns</spinner> + <deps> + <dep>failed_operation_listing.failed_operation_listing_data_source</dep> + </deps> + </settings> + <dataSource name="failed_operation_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Logging::system_magento_logging_bulk_operations</aclResource> + <dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="failed_operation_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <paging name="listing_paging"/> + <exportButton name="export_button"> + <settings> + <additionalParams> + <param xsi:type="string" name="uuid">${ $.provider}:params.uuid</param> + </additionalParams> + </settings> + </exportButton> + </listingToolbar> + <columns name="failed_operation_listing_columns"> + <settings> + <dndConfig> + <param name="enabled" xsi:type="boolean">false</param> + </dndConfig> + </settings> + <selectionsColumn name="ids"> + <settings> + <indexField>id</indexField> + <visible>false</visible> + </settings> + </selectionsColumn> + <column name="id" component="Magento_Ui/js/grid/columns/link" sortOrder="10"> + <settings> + <label translate="true">ID</label> + <sortable>false</sortable> + </settings> + </column> + <column name="meta_information" sortOrder="20"> + <settings> + <label translate="true">Meta Information</label> + <sortable>false</sortable> + </settings> + </column> + <column name="result_message" sortOrder="30"> + <settings> + <label translate="true">Error</label> + </settings> + </column> + </columns> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_modal_listing.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_modal_listing.xml new file mode 100644 index 0000000000000..62a4935da8ba7 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/failed_operation_modal_listing.xml @@ -0,0 +1,75 @@ +<?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">failed_operation_modal_listing.failed_operation_modal_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>failed_operation_modal_listing_columns</spinner> + <deps> + <dep>failed_operation_modal_listing.failed_operation_modal_listing_data_source</dep> + </deps> + </settings> + <dataSource name="failed_operation_modal_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Logging::system_magento_logging_bulk_operations</aclResource> + <dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="failed_operation_modal_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <paging name="listing_paging"/> + <exportButton name="export_button"> + <settings> + <additionalParams> + <param xsi:type="string" name="uuid">${ $.provider}:params.uuid</param> + </additionalParams> + </settings> + </exportButton> + </listingToolbar> + <columns name="failed_operation_modal_listing_columns"> + <settings> + <dndConfig> + <param name="enabled" xsi:type="boolean">false</param> + </dndConfig> + </settings> + <selectionsColumn name="ids"> + <settings> + <indexField>id</indexField> + <visible>false</visible> + </settings> + </selectionsColumn> + <column name="id" component="Magento_Ui/js/grid/columns/link" sortOrder="10"> + <settings> + <label translate="true">ID</label> + <sortable>false</sortable> + </settings> + </column> + <column name="meta_information" sortOrder="20"> + <settings> + <label translate="true">Meta Information</label> + <sortable>false</sortable> + </settings> + </column> + <column name="result_message" sortOrder="30"> + <settings> + <label translate="true">Error</label> + </settings> + </column> + </columns> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/notification_area.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/notification_area.xml new file mode 100644 index 0000000000000..b43a7e6f4c358 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/notification_area.xml @@ -0,0 +1,54 @@ +<?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"> + <columns name="columns" component="Magento_AsynchronousOperations/js/grid/listing" template="Magento_AsynchronousOperations/grid/listing"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="link" xsi:type="url" path="bulk/index"/> + <item name="linkText" xsi:type="string" translate="true">Bulk Actions Log</item> + <item name="dismissAllText" xsi:type="string" translate="true">Dismiss All Completed Tasks</item> + <item name="dismissUrl" xsi:type="url" path="bulk/notification/dismiss"/> + </item> + </argument> + <actionsColumn name="actions" class="Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationActions" sortOrder="20"> + <settings> + <indexField>identity</indexField> + </settings> + </actionsColumn> + <actionsColumn name="dismiss" class="Magento\AsynchronousOperations\Ui\Component\Listing\Column\NotificationDismissActions" sortOrder="10"> + <settings> + <indexField>identity</indexField> + <bodyTmpl>Magento_AsynchronousOperations/grid/cells/actions</bodyTmpl> + </settings> + </actionsColumn> + </columns> + <container name="modalContainer"> + <modal name="modal"> + <insertForm name="insertBulk" component="Magento_AsynchronousOperations/js/insert-form"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="titlePrefix" xsi:type="string" translate="true">Action Details - #</item> + <item name="modalTitleProvider" xsi:type="string">${ $.externalProvider }:data.uuid</item> + </item> + </argument> + <settings> + <formSubmitType>ajax</formSubmitType> + <columnsProvider>ns = notification_area, index = columns</columnsProvider> + <renderUrl path="mui/index/render_handle"> + <param name="handle">bulk_bulk_details_modal</param> + <param name="buttons">1</param> + </renderUrl> + <loading>false</loading> + <toolbarContainer>${ $.parentName }</toolbarContainer> + <externalProvider>${ $.ns }.bulk_details_form_modal_data_source</externalProvider> + <ns>bulk_details_form_modal</ns> + </settings> + </insertForm> + </modal> + </container> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_listing.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_listing.xml new file mode 100644 index 0000000000000..3618e10ee77d8 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_listing.xml @@ -0,0 +1,60 @@ +<?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">retriable_operation_listing.retriable_operation_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>retriable_operation_listing_columns</spinner> + <deps> + <dep>retriable_operation_listing.retriable_operation_listing_data_source</dep> + </deps> + </settings> + <dataSource name="retriable_operation_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Logging::system_magento_logging_bulk_operations</aclResource> + <dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="retriable_operation_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <paging name="listing_paging"/> + </listingToolbar> + <columns name="retriable_operation_listing_columns"> + <settings> + <dndConfig> + <param name="enabled" xsi:type="boolean">false</param> + </dndConfig> + </settings> + <selectionsColumn name="ids" sortOrder="10"> + <settings> + <indexField>error_code</indexField> + </settings> + </selectionsColumn> + <column name="result_message" sortOrder="20"> + <settings> + <label translate="true">Error</label> + </settings> + </column> + <column name="records_qty" sortOrder="30"> + <settings> + <label translate="true">Number of Records Affected</label> + </settings> + </column> + </columns> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_modal_listing.xml b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_modal_listing.xml new file mode 100644 index 0000000000000..97e3e897c2533 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/ui_component/retriable_operation_modal_listing.xml @@ -0,0 +1,60 @@ +<?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">retriable_operation_modal_listing.retriable_operation_modal_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>retriable_operation_modal_listing_columns</spinner> + <deps> + <dep>retriable_operation_modal_listing.retriable_operation_modal_listing_data_source</dep> + </deps> + </settings> + <dataSource name="retriable_operation_modal_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_Logging::system_magento_logging_bulk_operations</aclResource> + <dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="retriable_operation_modal_listing_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <paging name="listing_paging"/> + </listingToolbar> + <columns name="retriable_operation_modal_listing_columns"> + <settings> + <dndConfig> + <param name="enabled" xsi:type="boolean">false</param> + </dndConfig> + </settings> + <selectionsColumn name="ids" sortOrder="10"> + <settings> + <indexField>error_code</indexField> + </settings> + </selectionsColumn> + <column name="result_message" sortOrder="20"> + <settings> + <label translate="true">Error</label> + </settings> + </column> + <column name="records_qty" sortOrder="30"> + <settings> + <label translate="true">Number of Records Affected</label> + </settings> + </column> + </columns> +</listing> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/form/error.js b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/form/error.js new file mode 100644 index 0000000000000..bc41946657df1 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/form/error.js @@ -0,0 +1,17 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/abstract' +], function (Abstract) { + 'use strict'; + + return Abstract.extend({ + /** @inheritdoc */ + onUpdate: function () { + this.bubble('update', this.hasChanged()); + } + }); +}); diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/grid/listing.js b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/grid/listing.js new file mode 100644 index 0000000000000..104ac0ae1ec10 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/grid/listing.js @@ -0,0 +1,88 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_AdminNotification/js/grid/listing', + 'Magento_Ui/js/modal/alert', + 'mage/translate', + 'underscore', + 'jquery' +], function (Listing, uiAlert, $t, _, $) { + 'use strict'; + + return Listing.extend({ + defaults: { + isAllowed: true, + ajaxSettings: { + method: 'POST', + data: {}, + url: '${ $.dismissUrl }' + } + }, + + /** @inheritdoc */ + initialize: function () { + _.bindAll(this, 'reload', 'onError'); + + return this._super(); + }, + + /** + * Dismiss all items. + */ + dismissAll: function () { + var toDismiss = []; + + _.each(this.rows, function (row) { + if (row.dismiss) { + toDismiss.push(row.uuid); + } + }); + toDismiss.length && this.dismiss(toDismiss); + }, + + /** + * Dismiss action. + * + * @param {Array} items + */ + dismiss: function (items) { + var config = _.extend({}, this.ajaxSettings); + + config.data.uuid = items; + this.showLoader(); + + $.ajax(config) + .done(this.reload) + .fail(this.onError); + }, + + /** + * Success callback for dismiss request. + */ + reload: function () { + this.source.reload({ + refresh: true + }); + }, + + /** + * Error callback for dismiss request. + * + * @param {Object} xhr + */ + onError: function (xhr) { + this.hideLoader(); + + if (xhr.statusText === 'abort') { + return; + } + + uiAlert({ + content: $t('Something went wrong.') + }); + } + }); +}); diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/insert-form.js b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/insert-form.js new file mode 100644 index 0000000000000..5c39534ea5e9a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/js/insert-form.js @@ -0,0 +1,65 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/components/insert-form', + 'uiRegistry' +], function (Insert, registry) { + 'use strict'; + + return Insert.extend({ + defaults: { + modalProvider: '${ $.parentName }', + titlePrefix: '', + imports: { + changeModalTitle: '${ $.modalProvider }:state' + }, + listens: { + responseData: 'afterRetry' + }, + modules: { + modal: '${ $.modalProvider }', + notificationListing: '${ $.columnsProvider }' + } + }, + + /** @inheritdoc */ + initConfig: function () { + var modalTitleProvider; + + this._super(); + modalTitleProvider = this.modalTitleProvider.split(':'); + this.modalTitleTarget = modalTitleProvider[0]; + this.modalTitlePath = modalTitleProvider[1]; + }, + + /** + * Change modal title. + * + * @param {Boolean} change + */ + changeModalTitle: function (change) { + if (change) { + registry.get(this.modalTitleTarget, function (target) { + this.modal().setTitle(this.titlePrefix + target.get(this.modalTitlePath)); + }.bind(this)); + } else { + this.modal().setTitle(''); + } + }, + + /** + * Action after retry operation. + * + * @param {Object} data + */ + afterRetry: function (data) { + if (!data.error) { + this.modal().closeModal(); + this.notificationListing().reload(); + } + } + }); +}); diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/form/field.html b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/form/field.html new file mode 100644 index 0000000000000..6cff5e3ca9732 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/form/field.html @@ -0,0 +1,9 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div css="$data.additionalClasses" + if="error" + text="error"/> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/cells/actions.html b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/cells/actions.html new file mode 100644 index 0000000000000..17144a22cd32c --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/cells/actions.html @@ -0,0 +1,15 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="action-menu-item action-close-wrapper"> + <button class="action-close" + repeat="foreach: $col.getVisibleActions($row()._rowIndex), item: '$action'" + click="$col.getActionHandler($action())" + attr="{ + title: $action().label + }" + /> +</div> diff --git a/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/listing.html b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/listing.html new file mode 100644 index 0000000000000..e6559a6ede37c --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/view/adminhtml/web/template/grid/listing.html @@ -0,0 +1,43 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div id="system_messages" class="message-system" collapsible visible="totalRecords"> + <div class="message-system-inner" outerClick="fixLoaderHeight.bind($data, true)"> + <div class="message-system-short"> + <button class="message-system-action-dropdown" toggleCollapsible> + <span> + <translate args="'System Messages'"/>: + <text args="totalRecords"/> + </span> + </button> + <div class="message-system-short-wrapper" if="rows[0]" repeat="foreach: [rows[0]], item: '$row'" visible="!$collapsible.opened()"> + <fastForEach args="data: getVisible(), as: '$col'" > + <render args="$col.getBody()"/> + </fastForEach> + </div> + </div> + <div class="message-system-collapsible"> + <ul class="message-system-list"> + <li repeat="foreach: rows, item: '$row'"> + <fastForEach args="data: getVisible(), as: '$col'" > + <render args="$col.getBody()"/> + </fastForEach> + </li> + </ul> + <div class="message-system-summary" if="isAllowed"> + <a class="action__message-log" + href="#" + click="dismissAll" + text="dismissAllText"/> + <a class="action__message-log" + attr="{ + href: link + }" + text="linkText"/> + </div> + </div> + </div> +</div> diff --git a/app/code/Magento/Authorization/Model/Acl/AclRetriever.php b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php index f22cbaf46332b..904c8d0ea7794 100644 --- a/app/code/Magento/Authorization/Model/Acl/AclRetriever.php +++ b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php @@ -84,7 +84,7 @@ public function getAllowedResourcesByUser($userType, $userId) $role = $this->_getUserRole($userType, $userId); if (!$role) { throw new AuthorizationException( - __('We can\'t find the role for the user you wanted.') + __("The role wasn't found for the user. Verify the role and try again.") ); } $allowedResources = $this->getAllowedResourcesByRole($role->getId()); diff --git a/app/code/Magento/Authorization/Model/ResourceModel/Role.php b/app/code/Magento/Authorization/Model/ResourceModel/Role.php index 633ae741b44a1..48fe65e7f8b92 100644 --- a/app/code/Magento/Authorization/Model/ResourceModel/Role.php +++ b/app/code/Magento/Authorization/Model/ResourceModel/Role.php @@ -68,6 +68,7 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $role) } if (!$role->getTreeLevel()) { + $treeLevel = 0; if ($role->getPid() > 0) { $select = $this->getConnection()->select()->from( $this->getMainTable(), @@ -79,8 +80,6 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $role) $binds = ['pid' => (int)$role->getPid()]; $treeLevel = $this->getConnection()->fetchOne($select, $binds); - } else { - $treeLevel = 0; } $role->setTreeLevel($treeLevel + 1); diff --git a/app/code/Magento/Authorization/Setup/InstallData.php b/app/code/Magento/Authorization/Setup/InstallData.php deleted file mode 100644 index b8b18706722a5..0000000000000 --- a/app/code/Magento/Authorization/Setup/InstallData.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Authorization\Setup; - -use Magento\Framework\Setup\InstallDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Authorization\Model\Acl\Role\Group as RoleGroup; -use Magento\Authorization\Model\UserContextInterface; - -/** - * @codeCoverageIgnore - */ -class InstallData implements InstallDataInterface -{ - /** - * Authorization factory - * - * @var AuthorizationFactory - */ - private $authFactory; - - /** - * Init - * - * @param AuthorizationFactory $authFactory - */ - public function __construct(AuthorizationFactory $authFactory) - { - $this->authFactory = $authFactory; - } - - /** - * {@inheritdoc} - */ - public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) - { - $roleCollection = $this->authFactory->createRoleCollection() - ->addFieldToFilter('parent_id', 0) - ->addFieldToFilter('tree_level', 1) - ->addFieldToFilter('role_type', RoleGroup::ROLE_TYPE) - ->addFieldToFilter('user_id', 0) - ->addFieldToFilter('user_type', UserContextInterface::USER_TYPE_ADMIN) - ->addFieldToFilter('role_name', 'Administrators'); - - if ($roleCollection->count() == 0) { - $admGroupRole = $this->authFactory->createRole()->setData( - [ - 'parent_id' => 0, - 'tree_level' => 1, - 'sort_order' => 1, - 'role_type' => RoleGroup::ROLE_TYPE, - 'user_id' => 0, - 'user_type' => UserContextInterface::USER_TYPE_ADMIN, - 'role_name' => 'Administrators', - ] - )->save(); - } else { - foreach ($roleCollection as $item) { - $admGroupRole = $item; - break; - } - } - - $rulesCollection = $this->authFactory->createRulesCollection() - ->addFieldToFilter('role_id', $admGroupRole->getId()) - ->addFieldToFilter('resource_id', 'all'); - - if ($rulesCollection->count() == 0) { - $this->authFactory->createRules()->setData( - [ - 'role_id' => $admGroupRole->getId(), - 'resource_id' => 'Magento_Backend::all', - 'privileges' => null, - 'permission' => 'allow', - ] - )->save(); - } else { - /** @var \Magento\Authorization\Model\Rules $rule */ - foreach ($rulesCollection as $rule) { - $rule->setData('resource_id', 'Magento_Backend::all')->save(); - } - } - - /** - * Delete rows by condition from authorization_rule - */ - $setup->startSetup(); - - $tableName = $setup->getTable('authorization_rule'); - if ($tableName) { - $setup->getConnection()->delete($tableName, ['resource_id = ?' => 'admin/system/tools/compiler']); - } - - $setup->endSetup(); - } -} diff --git a/app/code/Magento/Authorization/Setup/InstallSchema.php b/app/code/Magento/Authorization/Setup/InstallSchema.php deleted file mode 100644 index 9471b448ea3b4..0000000000000 --- a/app/code/Magento/Authorization/Setup/InstallSchema.php +++ /dev/null @@ -1,150 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Authorization\Setup; - -use Magento\Framework\Setup\InstallSchemaInterface; -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\SchemaSetupInterface; - -/** - * @codeCoverageIgnore - */ -class InstallSchema implements InstallSchemaInterface -{ - /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) - { - $installer = $setup; - - $installer->startSetup(); - - if (!$installer->getConnection()->isTableExists($installer->getTable('authorization_role'))) { - /** - * Create table 'authorization_role' - */ - $table = $installer->getConnection()->newTable( - $installer->getTable('authorization_role') - )->addColumn( - 'role_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], - 'Role ID' - )->addColumn( - 'parent_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Parent Role ID' - )->addColumn( - 'tree_level', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Role Tree Level' - )->addColumn( - 'sort_order', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Role Sort Order' - )->addColumn( - 'role_type', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 1, - ['nullable' => false, 'default' => '0'], - 'Role Type' - )->addColumn( - 'user_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'User ID' - )->addColumn( - 'user_type', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 16, - ['nullable' => true, 'default' => null], - 'User Type' - )->addColumn( - 'role_name', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 50, - ['nullable' => true, 'default' => null], - 'Role Name' - )->addIndex( - $installer->getIdxName('authorization_role', ['parent_id', 'sort_order']), - ['parent_id', 'sort_order'] - )->addIndex( - $installer->getIdxName('authorization_role', ['tree_level']), - ['tree_level'] - )->setComment( - 'Admin Role Table' - ); - $installer->getConnection()->createTable($table); - } - - if (!$installer->getConnection()->isTableExists($installer->getTable('authorization_rule'))) { - /** - * Create table 'authorization_rule' - */ - $table = $installer->getConnection()->newTable( - $installer->getTable('authorization_rule') - )->addColumn( - 'rule_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], - 'Rule ID' - )->addColumn( - 'role_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Role ID' - )->addColumn( - 'resource_id', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 255, - ['nullable' => true, 'default' => null], - 'Resource ID' - )->addColumn( - 'privileges', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 20, - ['nullable' => true], - 'Privileges' - )->addColumn( - 'permission', - \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, - 10, - [], - 'Permission' - )->addIndex( - $installer->getIdxName('authorization_rule', ['resource_id', 'role_id']), - ['resource_id', 'role_id'] - )->addIndex( - $installer->getIdxName('authorization_rule', ['role_id', 'resource_id']), - ['role_id', 'resource_id'] - )->addForeignKey( - $installer->getFkName('authorization_rule', 'role_id', 'authorization_role', 'role_id'), - 'role_id', - $installer->getTable('authorization_role'), - 'role_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - )->setComment( - 'Admin Rule Table' - ); - $installer->getConnection()->createTable($table); - } - - $installer->endSetup(); - } -} diff --git a/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php new file mode 100644 index 0000000000000..84992badf65db --- /dev/null +++ b/app/code/Magento/Authorization/Setup/Patch/Data/InitializeAuthRoles.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Authorization\Setup\Patch\Data; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; +use Magento\Authorization\Model\Acl\Role\Group as RoleGroup; +use Magento\Authorization\Model\UserContextInterface; + +/** + * Class InitializeAuthRoles + * @package Magento\Authorization\Setup\Patch + */ +class InitializeAuthRoles implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var \Magento\Authorization\Setup\AuthorizationFactory + */ + private $authFactory; + + /** + * InitializeAuthRoles constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + * @param \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + \Magento\Authorization\Setup\AuthorizationFactory $authorizationFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->authFactory = $authorizationFactory; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $roleCollection = $this->authFactory->createRoleCollection() + ->addFieldToFilter('parent_id', 0) + ->addFieldToFilter('tree_level', 1) + ->addFieldToFilter('role_type', RoleGroup::ROLE_TYPE) + ->addFieldToFilter('user_id', 0) + ->addFieldToFilter('user_type', UserContextInterface::USER_TYPE_ADMIN) + ->addFieldToFilter('role_name', 'Administrators'); + + if ($roleCollection->count() == 0) { + $admGroupRole = $this->authFactory->createRole()->setData( + [ + 'parent_id' => 0, + 'tree_level' => 1, + 'sort_order' => 1, + 'role_type' => RoleGroup::ROLE_TYPE, + 'user_id' => 0, + 'user_type' => UserContextInterface::USER_TYPE_ADMIN, + 'role_name' => 'Administrators', + ] + )->save(); + } else { + /** @var \Magento\Authorization\Model\ResourceModel\Role $item */ + foreach ($roleCollection as $item) { + $admGroupRole = $item; + break; + } + } + + $rulesCollection = $this->authFactory->createRulesCollection() + ->addFieldToFilter('role_id', $admGroupRole->getId()) + ->addFieldToFilter('resource_id', 'all'); + + if ($rulesCollection->count() == 0) { + $this->authFactory->createRules()->setData( + [ + 'role_id' => $admGroupRole->getId(), + 'resource_id' => 'Magento_Backend::all', + 'privileges' => null, + 'permission' => 'allow', + ] + )->save(); + } else { + /** @var \Magento\Authorization\Model\Rules $rule */ + foreach ($rulesCollection as $rule) { + $rule->setData('resource_id', 'Magento_Backend::all')->save(); + } + } + + /** + * Delete rows by condition from authorization_rule + */ + $tableName = $this->moduleDataSetup->getTable('authorization_rule'); + if ($tableName) { + $this->moduleDataSetup->getConnection()->delete( + $tableName, + ['resource_id = ?' => 'admin/system/tools/compiler'] + ); + } + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public static function getVersion() + { + return '2.0.0'; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backup/LICENSE.txt b/app/code/Magento/Authorization/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backup/LICENSE.txt rename to app/code/Magento/Authorization/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/LICENSE_AFL.txt b/app/code/Magento/Authorization/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/LICENSE_AFL.txt rename to app/code/Magento/Authorization/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Authorization/Test/Mftf/README.md b/app/code/Magento/Authorization/Test/Mftf/README.md new file mode 100644 index 0000000000000..1d44ab2e73052 --- /dev/null +++ b/app/code/Magento/Authorization/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Authorization Functional Tests + +The Functional Test Module for **Magento Authorization** module. diff --git a/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php b/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php index bd1a3616a746e..cd51c0f9bc4b8 100644 --- a/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php +++ b/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php @@ -60,7 +60,7 @@ public function testGetAllowedResourcesByUserTypeCustomer() /** * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage We can't find the role for the user you wanted. + * @expectedExceptionMessage The role wasn't found for the user. Verify the role and try again. */ public function testGetAllowedResourcesByUserRoleNotFound() { @@ -78,6 +78,9 @@ public function testGetAllowedResourcesByUser() ); } + /** + * @return AclRetriever + */ protected function createAclRetriever() { $this->roleMock = $this->createPartialMock(\Magento\Authorization\Model\Role::class, ['getId', '__wakeup']); diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index 65e0d2a57e36d..5f5e7c62ef83b 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -5,12 +5,11 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/framework": "100.3.*", - "magento/module-backend": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-backend": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Authorization/etc/db_schema.xml b/app/code/Magento/Authorization/etc/db_schema.xml new file mode 100644 index 0000000000000..45c02128bfc99 --- /dev/null +++ b/app/code/Magento/Authorization/etc/db_schema.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="authorization_role" resource="default" engine="innodb" comment="Admin Role Table"> + <column xsi:type="int" name="role_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Role ID"/> + <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" + default="0" comment="Parent Role ID"/> + <column xsi:type="smallint" name="tree_level" padding="5" unsigned="true" nullable="false" identity="false" + default="0" comment="Role Tree Level"/> + <column xsi:type="smallint" name="sort_order" padding="5" unsigned="true" nullable="false" identity="false" + default="0" comment="Role Sort Order"/> + <column xsi:type="varchar" name="role_type" nullable="false" length="1" default="0" comment="Role Type"/> + <column xsi:type="int" name="user_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" + comment="User ID"/> + <column xsi:type="varchar" name="user_type" nullable="true" length="16" comment="User Type"/> + <column xsi:type="varchar" name="role_name" nullable="true" length="50" comment="Role Name"/> + <constraint xsi:type="primary" name="PRIMARY"> + <column name="role_id"/> + </constraint> + <index name="AUTHORIZATION_ROLE_PARENT_ID_SORT_ORDER" indexType="btree"> + <column name="parent_id"/> + <column name="sort_order"/> + </index> + <index name="AUTHORIZATION_ROLE_TREE_LEVEL" indexType="btree"> + <column name="tree_level"/> + </index> + </table> + <table name="authorization_rule" resource="default" engine="innodb" comment="Admin Rule Table"> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Rule ID"/> + <column xsi:type="int" name="role_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" + comment="Role ID"/> + <column xsi:type="varchar" name="resource_id" nullable="true" length="255" comment="Resource ID"/> + <column xsi:type="varchar" name="privileges" nullable="true" length="20" comment="Privileges"/> + <column xsi:type="varchar" name="permission" nullable="true" length="10" comment="Permission"/> + <constraint xsi:type="primary" name="PRIMARY"> + <column name="rule_id"/> + </constraint> + <constraint xsi:type="foreign" name="AUTHORIZATION_RULE_ROLE_ID_AUTHORIZATION_ROLE_ROLE_ID" + table="authorization_rule" column="role_id" referenceTable="authorization_role" + referenceColumn="role_id" onDelete="CASCADE"/> + <index name="AUTHORIZATION_RULE_RESOURCE_ID_ROLE_ID" indexType="btree"> + <column name="resource_id"/> + <column name="role_id"/> + </index> + <index name="AUTHORIZATION_RULE_ROLE_ID_RESOURCE_ID" indexType="btree"> + <column name="role_id"/> + <column name="resource_id"/> + </index> + </table> +</schema> diff --git a/app/code/Magento/Authorization/etc/db_schema_whitelist.json b/app/code/Magento/Authorization/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..8c416d2a8b42c --- /dev/null +++ b/app/code/Magento/Authorization/etc/db_schema_whitelist.json @@ -0,0 +1,38 @@ +{ + "authorization_role": { + "column": { + "role_id": true, + "parent_id": true, + "tree_level": true, + "sort_order": true, + "role_type": true, + "user_id": true, + "user_type": true, + "role_name": true + }, + "index": { + "AUTHORIZATION_ROLE_PARENT_ID_SORT_ORDER": true, + "AUTHORIZATION_ROLE_TREE_LEVEL": true + }, + "constraint": { + "PRIMARY": true + } + }, + "authorization_rule": { + "column": { + "rule_id": true, + "role_id": true, + "resource_id": true, + "privileges": true, + "permission": true + }, + "index": { + "AUTHORIZATION_RULE_RESOURCE_ID_ROLE_ID": true, + "AUTHORIZATION_RULE_ROLE_ID_RESOURCE_ID": true + }, + "constraint": { + "PRIMARY": true, + "AUTHORIZATION_RULE_ROLE_ID_AUTHORIZATION_ROLE_ROLE_ID": true + } + } +} \ No newline at end of file diff --git a/app/code/Magento/Authorization/etc/module.xml b/app/code/Magento/Authorization/etc/module.xml index 357e36d937e50..145b1ba10d0f8 100644 --- a/app/code/Magento/Authorization/etc/module.xml +++ b/app/code/Magento/Authorization/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Authorization" setup_version="2.0.0"> + <module name="Magento_Authorization" > <sequence> <module name="Magento_Backend"/> </sequence> diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php index 3ad9f470909bf..70565ea8ac65f 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php @@ -10,12 +10,15 @@ use Magento\Authorizenet\Model\Directpost; use Magento\Authorizenet\Model\DirectpostFactory; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; use Psr\Log\LoggerInterface; -class BackendResponse extends \Magento\Authorizenet\Controller\Directpost\Payment +class BackendResponse extends \Magento\Authorizenet\Controller\Directpost\Payment implements CsrfAwareActionInterface { /** * @var LoggerInterface @@ -48,6 +51,23 @@ public function __construct( $this->logger = $logger ?: $this->_objectManager->get(LoggerInterface::class); } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Response action. * Action for Authorize.net SIM Relay Request. diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php index bdd0c4a424e99..3c1cb90e0c0a5 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Place.php @@ -3,8 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Authorizenet\Controller\Directpost\Payment; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Authorizenet\Controller\Directpost\Payment; use Magento\Authorizenet\Helper\DataFactory; use Magento\Checkout\Model\Type\Onepage; @@ -24,7 +26,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Place extends Payment +class Place extends Payment implements HttpPostActionInterface { /** * @var \Magento\Quote\Api\CartManagementInterface @@ -122,7 +124,7 @@ public function execute() /** * Place order for checkout flow * - * @return string + * @return void */ protected function placeCheckoutOrder() { @@ -147,7 +149,7 @@ protected function placeCheckoutOrder() $result->setData('error', true); $result->setData( 'error_messages', - __('An error occurred on the server. Please try to place the order again.') + __('A server error stopped your order from being placed. Please try to place your order again.') ); } if ($response instanceof Http) { diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php index d88e77d6c4e28..d562df9fb24a9 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php @@ -6,8 +6,29 @@ */ namespace Magento\Authorizenet\Controller\Directpost\Payment; -class Response extends \Magento\Authorizenet\Controller\Directpost\Payment +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +class Response extends \Magento\Authorizenet\Controller\Directpost\Payment implements CsrfAwareActionInterface { + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Response action. * Action for Authorize.net SIM Relay Request. diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index 0f10fd633cb5b..d5c11ab54cd94 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -5,10 +5,9 @@ */ namespace Magento\Authorizenet\Model; -use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Framework\App\ObjectManager; use Magento\Payment\Model\Method\ConfigInterface; use Magento\Payment\Model\Method\TransparentInterface; -use Magento\Sales\Model\Order\Email\Sender\OrderSender; /** * Authorize.net DirectPost payment method model. @@ -102,7 +101,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra protected $response; /** - * @var OrderSender + * @var \Magento\Sales\Model\Order\Email\Sender\OrderSender */ protected $orderSender; @@ -123,6 +122,16 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra */ private $psrLogger; + /** + * @var \Magento\Sales\Api\PaymentFailuresInterface + */ + private $paymentFailures; + + /** + * @var \Magento\Sales\Model\Order + */ + private $order; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -134,18 +143,19 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra * @param \Magento\Framework\Module\ModuleListInterface $moduleList * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Authorizenet\Helper\Data $dataHelper - * @param Directpost\Request\Factory $requestFactory - * @param Directpost\Response\Factory $responseFactory + * @param \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory + * @param \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory * @param \Magento\Authorizenet\Model\TransactionService $transactionService * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory * @param \Magento\Sales\Model\OrderFactory $orderFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param OrderSender $orderSender + * @param \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender * @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -161,8 +171,8 @@ public function __construct( \Magento\Authorizenet\Helper\Data $dataHelper, \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory, - TransactionService $transactionService, - ZendClientFactory $httpClientFactory, + \Magento\Authorizenet\Model\TransactionService $transactionService, + \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory, \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, @@ -170,7 +180,8 @@ public function __construct( \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null ) { $this->orderFactory = $orderFactory; $this->storeManager = $storeManager; @@ -179,6 +190,8 @@ public function __construct( $this->orderSender = $orderSender; $this->transactionRepository = $transactionRepository; $this->_code = static::METHOD_CODE; + $this->paymentFailures = $paymentFailures ? : ObjectManager::getInstance() + ->get(\Magento\Sales\Api\PaymentFailuresInterface::class); parent::__construct( $context, @@ -561,13 +574,10 @@ public function process(array $responseData) $this->validateResponse(); $response = $this->getResponse(); - //operate with order - $orderIncrementId = $response->getXInvoiceNum(); $responseText = $this->dataHelper->wrapGatewayError($response->getXResponseReasonText()); $isError = false; - if ($orderIncrementId) { - /* @var $order \Magento\Sales\Model\Order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderIncrementId); + if ($this->getOrderIncrementId()) { + $order = $this->getOrderFromResponse(); //check payment method $payment = $order->getPayment(); if (!$payment || $payment->getMethod() != $this->getCode()) { @@ -632,9 +642,10 @@ public function checkResponseCode() return true; case self::RESPONSE_CODE_DECLINED: case self::RESPONSE_CODE_ERROR: - throw new \Magento\Framework\Exception\LocalizedException( - $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText()) - ); + $errorMessage = $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText()); + $order = $this->getOrderFromResponse(); + $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMessage); + throw new \Magento\Framework\Exception\LocalizedException($errorMessage); default: throw new \Magento\Framework\Exception\LocalizedException( __('There was a payment authorization error.') @@ -803,10 +814,14 @@ protected function declineOrder(\Magento\Sales\Model\Order $order, $message = '' { try { $response = $this->getResponse(); - if ($voidPayment && $response->getXTransId() && strtoupper($response->getXType()) - == self::REQUEST_TYPE_AUTH_ONLY + if ($voidPayment + && $response->getXTransId() + && strtoupper($response->getXType()) == self::REQUEST_TYPE_AUTH_ONLY ) { - $order->getPayment()->setTransactionId(null)->setParentTransactionId($response->getXTransId())->void(); + $order->getPayment() + ->setTransactionId(null) + ->setParentTransactionId($response->getXTransId()) + ->void($response); } $order->registerCancellation($message)->save(); } catch (\Exception $e) { @@ -988,12 +1003,40 @@ protected function getTransactionResponse($transactionId) private function getPsrLogger() { if (null === $this->psrLogger) { - $this->psrLogger = \Magento\Framework\App\ObjectManager::getInstance() + $this->psrLogger = ObjectManager::getInstance() ->get(\Psr\Log\LoggerInterface::class); } return $this->psrLogger; } + /** + * Fetch order by increment id from response. + * + * @return \Magento\Sales\Model\Order + */ + private function getOrderFromResponse(): \Magento\Sales\Model\Order + { + if (!$this->order) { + $this->order = $this->orderFactory->create(); + + if ($incrementId = $this->getOrderIncrementId()) { + $this->order = $this->order->loadByIncrementId($incrementId); + } + } + + return $this->order; + } + + /** + * Fetch order increment id from response. + * + * @return string + */ + private function getOrderIncrementId(): string + { + return $this->getResponse()->getXInvoiceNum(); + } + /** * Checks if filter action is Report Only. Transactions that trigger this filter are processed as normal, * but are also reported in the Merchant Interface as triggering this filter. diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request.php b/app/code/Magento/Authorizenet/Model/Directpost/Request.php index d9a403e5c991e..fc78d836b6080 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Request.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Request.php @@ -112,50 +112,50 @@ public function setDataFromOrder( sprintf('%.2F', $order->getBaseShippingAmount()) ); - //need to use strval() because NULL values IE6-8 decodes as "null" in JSON in JavaScript, + //need to use (string) because NULL values IE6-8 decodes as "null" in JSON in JavaScript, //but we need "" for null values. $billing = $order->getBillingAddress(); if (!empty($billing)) { - $this->setXFirstName(strval($billing->getFirstname())) - ->setXLastName(strval($billing->getLastname())) - ->setXCompany(strval($billing->getCompany())) - ->setXAddress(strval($billing->getStreetLine(1))) - ->setXCity(strval($billing->getCity())) - ->setXState(strval($billing->getRegion())) - ->setXZip(strval($billing->getPostcode())) - ->setXCountry(strval($billing->getCountryId())) - ->setXPhone(strval($billing->getTelephone())) - ->setXFax(strval($billing->getFax())) - ->setXCustId(strval($billing->getCustomerId())) - ->setXCustomerIp(strval($order->getRemoteIp())) - ->setXCustomerTaxId(strval($billing->getTaxId())) - ->setXEmail(strval($order->getCustomerEmail())) - ->setXEmailCustomer(strval($paymentMethod->getConfigData('email_customer'))) - ->setXMerchantEmail(strval($paymentMethod->getConfigData('merchant_email'))); + $this->setXFirstName((string)$billing->getFirstname()) + ->setXLastName((string)$billing->getLastname()) + ->setXCompany((string)$billing->getCompany()) + ->setXAddress((string)$billing->getStreetLine(1)) + ->setXCity((string)$billing->getCity()) + ->setXState((string)$billing->getRegion()) + ->setXZip((string)$billing->getPostcode()) + ->setXCountry((string)$billing->getCountryId()) + ->setXPhone((string)$billing->getTelephone()) + ->setXFax((string)$billing->getFax()) + ->setXCustId((string)$billing->getCustomerId()) + ->setXCustomerIp((string)$order->getRemoteIp()) + ->setXCustomerTaxId((string)$billing->getTaxId()) + ->setXEmail((string)$order->getCustomerEmail()) + ->setXEmailCustomer((string)$paymentMethod->getConfigData('email_customer')) + ->setXMerchantEmail((string)$paymentMethod->getConfigData('merchant_email')); } $shipping = $order->getShippingAddress(); if (!empty($shipping)) { $this->setXShipToFirstName( - strval($shipping->getFirstname()) + (string)$shipping->getFirstname() )->setXShipToLastName( - strval($shipping->getLastname()) + (string)$shipping->getLastname() )->setXShipToCompany( - strval($shipping->getCompany()) + (string)$shipping->getCompany() )->setXShipToAddress( - strval($shipping->getStreetLine(1)) + (string)$shipping->getStreetLine(1) )->setXShipToCity( - strval($shipping->getCity()) + (string)$shipping->getCity() )->setXShipToState( - strval($shipping->getRegion()) + (string)$shipping->getRegion() )->setXShipToZip( - strval($shipping->getPostcode()) + (string)$shipping->getPostcode() )->setXShipToCountry( - strval($shipping->getCountryId()) + (string)$shipping->getCountryId() ); } - $this->setXPoNum(strval($payment->getPoNumber())); + $this->setXPoNum((string)$payment->getPoNumber()); return $this; } diff --git a/app/code/Magento/Authorizenet/Model/TransactionService.php b/app/code/Magento/Authorizenet/Model/TransactionService.php index fef22d6c913c0..693a5b890faba 100644 --- a/app/code/Magento/Authorizenet/Model/TransactionService.php +++ b/app/code/Magento/Authorizenet/Model/TransactionService.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Authorizenet\Model; use Magento\Framework\Exception\LocalizedException; @@ -124,7 +125,7 @@ protected function loadTransactionDetails(Authorizenet $context, $transactionId) $responseXmlDocument = new Element($responseBody); libxml_use_internal_errors(false); } catch (\Exception $e) { - throw new LocalizedException(__('Unable to get transaction details. Try again later.')); + throw new LocalizedException(__('The transaction details are unavailable. Please try again later.')); } finally { $context->debugData($debugData); } @@ -132,7 +133,7 @@ protected function loadTransactionDetails(Authorizenet $context, $transactionId) if (!isset($responseXmlDocument->messages->resultCode) || $responseXmlDocument->messages->resultCode != static::PAYMENT_UPDATE_STATUS_CODE_SUCCESS ) { - throw new LocalizedException(__('Unable to get transaction details. Try again later.')); + throw new LocalizedException(__('The transaction details are unavailable. Please try again later.')); } $this->transactionDetails[$transactionId] = $responseXmlDocument; diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Braintree/LICENSE.txt b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Braintree/LICENSE.txt rename to app/code/Magento/Authorizenet/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backup/LICENSE_AFL.txt b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backup/LICENSE_AFL.txt rename to app/code/Magento/Authorizenet/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Authorizenet/Test/Mftf/README.md b/app/code/Magento/Authorizenet/Test/Mftf/README.md new file mode 100644 index 0000000000000..9391126a85c94 --- /dev/null +++ b/app/code/Magento/Authorizenet/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Authorizenet Functional Tests + +The Functional Test Module for **Magento Authorizenet** module. diff --git a/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/PlaceTest.php b/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/PlaceTest.php index 95ceed1ee11e7..c0a50e66759ba 100644 --- a/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/PlaceTest.php +++ b/app/code/Magento/Authorizenet/Test/Unit/Controller/Directpost/Payment/PlaceTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Authorizenet\Test\Unit\Controller\Directpost\Payment; use Magento\Authorizenet\Controller\Directpost\Payment\Place; @@ -297,7 +298,9 @@ public function textExecuteFailedPlaceOrderDataProvider() $objectFailed1 = new \Magento\Framework\DataObject( [ 'error' => true, - 'error_messages' => __('An error occurred on the server. Please try to place the order again.') + 'error_messages' => __( + 'A server error stopped your order from being placed. Please try to place your order again.' + ) ] ); $generalException = new \Exception('Exception logging will save the world!'); diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php index 6e5d55e52675e..15c7eecb09a69 100644 --- a/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php +++ b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php @@ -37,6 +37,9 @@ public function testGenerateHash($merchantMd5, $merchantApiLogin, $amount, $amou ); } + /** + * @return array + */ public function generateHashDataProvider() { return [ @@ -57,6 +60,13 @@ public function generateHashDataProvider() ]; } + /** + * @param $merchantMd5 + * @param $merchantApiLogin + * @param $amount + * @param $transactionId + * @return string + */ protected function generateHash($merchantMd5, $merchantApiLogin, $amount, $transactionId) { return strtoupper(md5($merchantMd5 . $merchantApiLogin . $transactionId . $amount)); diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php index dbb6ac8333c14..95c67f67852da 100644 --- a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php +++ b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Authorizenet\Test\Unit\Model; +use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Framework\Simplexml\Element; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Authorizenet\Model\Directpost; @@ -74,6 +75,14 @@ class DirectpostTest extends \PHPUnit\Framework\TestCase */ protected $requestFactory; + /** + * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentFailures; + + /** + * @inheritdoc + */ protected function setUp() { $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) @@ -104,6 +113,12 @@ protected function setUp() ->setMethods(['getTransactionDetails']) ->getMock(); + $this->paymentFailures = $this->getMockBuilder( + PaymentFailuresInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->requestFactory = $this->getRequestFactoryMock(); $httpClientFactoryMock = $this->getHttpClientFactoryMock(); @@ -117,7 +132,8 @@ protected function setUp() 'responseFactory' => $this->responseFactoryMock, 'transactionRepository' => $this->transactionRepositoryMock, 'transactionService' => $this->transactionServiceMock, - 'httpClientFactory' => $httpClientFactoryMock + 'httpClientFactory' => $httpClientFactoryMock, + 'paymentFailures' => $this->paymentFailures, ] ); } @@ -313,12 +329,16 @@ public function checkResponseCodeSuccessDataProvider() } /** - * @param bool $responseCode + * Checks response failures behaviour. + * + * @param int $responseCode + * @param int $failuresHandlerCalls + * @return void * * @expectedException \Magento\Framework\Exception\LocalizedException * @dataProvider checkResponseCodeFailureDataProvider */ - public function testCheckResponseCodeFailure($responseCode) + public function testCheckResponseCodeFailure(int $responseCode, int $failuresHandlerCalls): void { $reasonText = 'reason text'; @@ -333,18 +353,35 @@ public function testCheckResponseCodeFailure($responseCode) ->with($reasonText) ->willReturn(__('Gateway error: %1', $reasonText)); + $orderMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + + $orderMock->expects($this->exactly($failuresHandlerCalls)) + ->method('getQuoteId') + ->willReturn(1); + + $this->paymentFailures->expects($this->exactly($failuresHandlerCalls)) + ->method('handle') + ->with(1); + + $reflection = new \ReflectionClass($this->directpost); + $order = $reflection->getProperty('order'); + $order->setAccessible(true); + $order->setValue($this->directpost, $orderMock); + $this->directpost->checkResponseCode(); } /** * @return array */ - public function checkResponseCodeFailureDataProvider() + public function checkResponseCodeFailureDataProvider(): array { return [ - ['responseCode' => Directpost::RESPONSE_CODE_DECLINED], - ['responseCode' => Directpost::RESPONSE_CODE_ERROR], - ['responseCode' => 999999] + ['responseCode' => Directpost::RESPONSE_CODE_DECLINED, 1], + ['responseCode' => Directpost::RESPONSE_CODE_ERROR, 1], + ['responseCode' => 999999, 0], ]; } diff --git a/app/code/Magento/Authorizenet/composer.json b/app/code/Magento/Authorizenet/composer.json index 89d3ba8045a40..4e646004d9a6d 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -5,23 +5,23 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/framework": "100.3.*", - "magento/module-backend": "100.3.*", - "magento/module-catalog": "101.2.*", - "magento/module-checkout": "100.3.*", - "magento/module-payment": "100.3.*", - "magento/module-quote": "100.3.*", - "magento/module-sales": "100.3.*", - "magento/module-store": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-catalog": "*", + "magento/module-checkout": "*", + "magento/module-payment": "*", + "magento/module-quote": "*", + "magento/module-sales": "*", + "magento/module-store": "*" }, "suggest": { - "magento/module-config": "100.3.*" + "magento/module-config": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Authorizenet/etc/module.xml b/app/code/Magento/Authorizenet/etc/module.xml index 6d05f14d21318..a30fd34927746 100644 --- a/app/code/Magento/Authorizenet/etc/module.xml +++ b/app/code/Magento/Authorizenet/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Authorizenet" setup_version="2.0.0"> + <module name="Magento_Authorizenet" > <sequence> <module name="Magento_Sales"/> <module name="Magento_Quote"/> diff --git a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js index 8edc38dce6f60..2b57e5cc2fb0d 100644 --- a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js +++ b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js @@ -6,7 +6,7 @@ var config = { map: { '*': { - transparent: 'Magento_Payment/transparent' + transparent: 'Magento_Payment/js/transparent' } } }; diff --git a/app/code/Magento/Backend/App/AbstractAction.php b/app/code/Magento/Backend/App/AbstractAction.php index 99ee86b2b6407..fb2daa283f111 100644 --- a/app/code/Magento/Backend/App/AbstractAction.php +++ b/app/code/Magento/Backend/App/AbstractAction.php @@ -205,10 +205,6 @@ private function _moveBlockToContainer(\Magento\Framework\View\Element\AbstractB */ public function dispatch(\Magento\Framework\App\RequestInterface $request) { - if (!$this->_processUrlKeys()) { - return parent::dispatch($request); - } - if ($request->isDispatched() && $request->getActionName() !== 'denied' && !$this->_isAllowed()) { $this->_response->setStatusHeader(403, '1.1', 'Forbidden'); if (!$this->_auth->isLoggedIn()) { @@ -217,6 +213,7 @@ public function dispatch(\Magento\Framework\App\RequestInterface $request) $this->_view->loadLayout(['default', 'adminhtml_denied'], true, true, false); $this->_view->renderLayout(); $this->_request->setDispatched(true); + return $this->_response; } @@ -226,6 +223,11 @@ public function dispatch(\Magento\Framework\App\RequestInterface $request) $this->_processLocaleSettings(); + // Need to preload isFirstPageAfterLogin (see https://github.com/magento/magento2/issues/15510) + if ($this->_auth->isLoggedIn()) { + $this->_auth->getAuthStorage()->isFirstPageAfterLogin(); + } + return parent::dispatch($request); } @@ -246,6 +248,9 @@ protected function _isUrlChecked() * Check url keys. If non valid - redirect * * @return bool + * + * @see \Magento\Backend\App\Request\BackendValidator for default + * request validation. */ public function _processUrlKeys() { diff --git a/app/code/Magento/Backend/App/Action/Plugin/Authentication.php b/app/code/Magento/Backend/App/Action/Plugin/Authentication.php index 68506a521c1cf..4b25e9921e404 100644 --- a/app/code/Magento/Backend/App/Action/Plugin/Authentication.php +++ b/app/code/Magento/Backend/App/Action/Plugin/Authentication.php @@ -160,7 +160,7 @@ protected function _processNotLoggedInUser(\Magento\Framework\App\RequestInterfa } else { $this->_actionFlag->set('', \Magento\Framework\App\ActionInterface::FLAG_NO_DISPATCH, true); $this->_response->setRedirect($this->_url->getCurrentUrl()); - $this->messageManager->addError(__('Invalid Form Key. Please refresh the page.')); + $this->messageManager->addErrorMessage(__('Invalid Form Key. Please refresh the page.')); $isRedirectNeeded = true; } } @@ -205,7 +205,7 @@ protected function _performLogin(\Magento\Framework\App\RequestInterface $reques $this->_auth->login($username, $password); } catch (AuthenticationException $e) { if (!$request->getParam('messageSent')) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $request->setParam('messageSent', true); $outputValue = false; } diff --git a/app/code/Magento/Backend/App/DefaultPath.php b/app/code/Magento/Backend/App/DefaultPath.php index df8b718389741..b790a2edc3fab 100644 --- a/app/code/Magento/Backend/App/DefaultPath.php +++ b/app/code/Magento/Backend/App/DefaultPath.php @@ -42,6 +42,6 @@ public function __construct(\Magento\Backend\App\ConfigInterface $config) */ public function getPart($code) { - return isset($this->_parts[$code]) ? $this->_parts[$code] : null; + return $this->_parts[$code] ?? null; } } diff --git a/app/code/Magento/Backend/App/Request/BackendValidator.php b/app/code/Magento/Backend/App/Request/BackendValidator.php new file mode 100644 index 0000000000000..878f9cb4dc4c1 --- /dev/null +++ b/app/code/Magento/Backend/App/Request/BackendValidator.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\App\Request; + +use Magento\Backend\App\AbstractAction; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\Request\ValidatorInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Backend\Model\Auth; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Controller\Result\RawFactory; +use Magento\Framework\Controller\Result\Raw as RawResult; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; +use Magento\Backend\Model\UrlInterface as BackendUrl; +use Magento\Framework\Phrase; + +/** + * Do backend validations. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BackendValidator implements ValidatorInterface +{ + /** + * @var Auth + */ + private $auth; + + /** + * @var FormKeyValidator + */ + private $formKeyValidator; + + /** + * @var BackendUrl + */ + private $backendUrl; + + /** + * @var RedirectFactory + */ + private $redirectFactory; + + /** + * @var RawFactory + */ + private $rawResultFactory; + + /** + * @param Auth $auth + * @param FormKeyValidator $formKeyValidator + * @param BackendUrl $backendUrl + * @param RedirectFactory $redirectFactory + * @param RawFactory $rawResultFactory + */ + public function __construct( + Auth $auth, + FormKeyValidator $formKeyValidator, + BackendUrl $backendUrl, + RedirectFactory $redirectFactory, + RawFactory $rawResultFactory + ) { + $this->auth = $auth; + $this->formKeyValidator = $formKeyValidator; + $this->backendUrl = $backendUrl; + $this->redirectFactory = $redirectFactory; + $this->rawResultFactory = $rawResultFactory; + } + + /** + * @param RequestInterface $request + * @param ActionInterface $action + * + * @return bool + */ + private function validateRequest( + RequestInterface $request, + ActionInterface $action + ): bool { + /** @var bool|null $valid */ + $valid = null; + + if ($action instanceof CsrfAwareActionInterface) { + $valid = $action->validateForCsrf($request); + } + + if ($valid === null) { + $validFormKey = true; + $validSecretKey = true; + if ($request instanceof HttpRequest && $request->isPost()) { + $validFormKey = $this->formKeyValidator->validate($request); + } elseif ($this->auth->isLoggedIn() + && $this->backendUrl->useSecretKey() + ) { + $secretKeyValue = (string)$request->getParam( + BackendUrl::SECRET_KEY_PARAM_NAME, + null + ); + $secretKey = $this->backendUrl->getSecretKey(); + $validSecretKey = ($secretKeyValue === $secretKey); + } + $valid = $validFormKey && $validSecretKey; + } + + return $valid; + } + + /** + * @param RequestInterface $request + * @param ActionInterface $action + * + * @return InvalidRequestException + */ + private function createException( + RequestInterface $request, + ActionInterface $action + ): InvalidRequestException { + /** @var InvalidRequestException|null $exception */ + $exception = null; + + if ($action instanceof CsrfAwareActionInterface) { + $exception = $action->createCsrfValidationException($request); + } + + if ($exception === null) { + if ($request instanceof HttpRequest && $request->isAjax()) { + //Sending empty response for AJAX request since we don't know + //the expected response format and it's pointless to redirect. + /** @var RawResult $response */ + $response = $this->rawResultFactory->create(); + $response->setHttpResponseCode(401); + $response->setContents(''); + $exception = new InvalidRequestException($response); + } else { + //For regular requests. + $response = $this->redirectFactory->create() + ->setUrl($this->backendUrl->getStartupPageUrl()); + $exception = new InvalidRequestException( + $response, + [ + new Phrase( + 'Invalid security or form key. Please refresh the page.' + ) + ] + ); + } + } + + return $exception; + } + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + if ($action instanceof AbstractAction) { + //Abstract Action has build-in validation. + if (!$action->_processUrlKeys()) { + throw new InvalidRequestException($action->getResponse()); + } + } else { + //Fallback validation. + if (!$this->validateRequest($request, $action)) { + throw $this->createException($request, $action); + } + } + } +} diff --git a/app/code/Magento/Backend/Block/Cache.php b/app/code/Magento/Backend/Block/Cache.php index e14358396aa70..82c36bf3a1fe4 100644 --- a/app/code/Magento/Backend/Block/Cache.php +++ b/app/code/Magento/Backend/Block/Cache.php @@ -22,24 +22,29 @@ protected function _construct() $this->_headerText = __('Cache Storage Management'); parent::_construct(); $this->buttonList->remove('add'); - $this->buttonList->add( - 'flush_magento', - [ - 'label' => __('Flush Magento Cache'), - 'onclick' => 'setLocation(\'' . $this->getFlushSystemUrl() . '\')', - 'class' => 'primary flush-cache-magento' - ] - ); - $message = __('The cache storage may contain additional data. Are you sure that you want to flush it?'); - $this->buttonList->add( - 'flush_system', - [ - 'label' => __('Flush Cache Storage'), - 'onclick' => 'confirmSetLocation(\'' . $message . '\', \'' . $this->getFlushStorageUrl() . '\')', - 'class' => 'flush-cache-storage' - ] - ); + if ($this->_authorization->isAllowed('Magento_Backend::flush_magento_cache')) { + $this->buttonList->add( + 'flush_magento', + [ + 'label' => __('Flush Magento Cache'), + 'onclick' => 'setLocation(\'' . $this->getFlushSystemUrl() . '\')', + 'class' => 'primary flush-cache-magento' + ] + ); + } + + if ($this->_authorization->isAllowed('Magento_Backend::flush_cache_storage')) { + $message = __('The cache storage may contain additional data. Are you sure that you want to flush it?'); + $this->buttonList->add( + 'flush_system', + [ + 'label' => __('Flush Cache Storage'), + 'onclick' => 'confirmSetLocation(\'' . $message . '\', \'' . $this->getFlushStorageUrl() . '\')', + 'class' => 'flush-cache-storage' + ] + ); + } } /** diff --git a/app/code/Magento/Backend/Block/Cache/Permissions.php b/app/code/Magento/Backend/Block/Cache/Permissions.php new file mode 100644 index 0000000000000..272a603145f09 --- /dev/null +++ b/app/code/Magento/Backend/Block/Cache/Permissions.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Backend\Block\Cache; + +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Class Permissions + */ +class Permissions implements ArgumentInterface +{ + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * Permissions constructor. + * + * @param AuthorizationInterface $authorization + */ + public function __construct(AuthorizationInterface $authorization) + { + $this->authorization = $authorization; + } + + /** + * @return bool + */ + public function hasAccessToFlushCatalogImages() + { + return $this->authorization->isAllowed('Magento_Backend::flush_catalog_images'); + } + /** + * @return bool + */ + public function hasAccessToFlushJsCss() + { + return $this->authorization->isAllowed('Magento_Backend::flush_js_css'); + } + /** + * @return bool + */ + public function hasAccessToFlushStaticFiles() + { + return $this->authorization->isAllowed('Magento_Backend::flush_static_files'); + } + /** + * @return bool + */ + public function hasAccessToAdditionalActions() + { + return ($this->hasAccessToFlushCatalogImages() + || $this->hasAccessToFlushJsCss() + || $this->hasAccessToFlushStaticFiles()); + } +} diff --git a/app/code/Magento/Backend/Block/Dashboard.php b/app/code/Magento/Backend/Block/Dashboard.php index 8d0a061621fe3..e1e87d8d4c5a3 100644 --- a/app/code/Magento/Backend/Block/Dashboard.php +++ b/app/code/Magento/Backend/Block/Dashboard.php @@ -20,7 +20,7 @@ class Dashboard extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'dashboard/index.phtml'; + protected $_template = 'Magento_Backend::dashboard/index.phtml'; /** * @return void diff --git a/app/code/Magento/Backend/Block/Dashboard/Bar.php b/app/code/Magento/Backend/Block/Dashboard/Bar.php index 29557f12c1093..7ccb2d51ccd1b 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Bar.php +++ b/app/code/Magento/Backend/Block/Dashboard/Bar.php @@ -38,14 +38,6 @@ public function getTotals() */ public function addTotal($label, $value, $isQuantity = false) { - /*if (!$isQuantity) { - $value = $this->format($value); - $decimals = substr($value, -2); - $value = substr($value, 0, -2); - } else { - $value = ($value != '')?$value:0; - $decimals = ''; - }*/ if (!$isQuantity) { $value = $this->format($value); } diff --git a/app/code/Magento/Backend/Block/Dashboard/Graph.php b/app/code/Magento/Backend/Block/Dashboard/Graph.php index 301dffbdc4987..8e238ccab44cb 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Graph.php +++ b/app/code/Magento/Backend/Block/Dashboard/Graph.php @@ -90,7 +90,7 @@ class Graph extends \Magento\Backend\Block\Dashboard\AbstractDashboard /** * @var string */ - protected $_template = 'dashboard/graph.phtml'; + protected $_template = 'Magento_Backend::dashboard/graph.phtml'; /** * Adminhtml dashboard data diff --git a/app/code/Magento/Backend/Block/Dashboard/Grid.php b/app/code/Magento/Backend/Block/Dashboard/Grid.php index 602b5e414d538..f7f9a79f17eb0 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Grid.php +++ b/app/code/Magento/Backend/Block/Dashboard/Grid.php @@ -17,7 +17,7 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended /** * @var string */ - protected $_template = 'dashboard/grid.phtml'; + protected $_template = 'Magento_Backend::dashboard/grid.phtml'; /** * Setting default for every grid on dashboard diff --git a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php index 9d9409fba093b..50279786c0a5b 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php +++ b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php @@ -92,7 +92,7 @@ protected function _prepareCollection() protected function _afterLoadCollection() { foreach ($this->getCollection() as $item) { - $item->getCustomer() ?: $item->setCustomer('Guest'); + $item->getCustomer() ?: $item->setCustomer($item->getBillingAddress()->getName()); } return $this; } diff --git a/app/code/Magento/Backend/Block/Dashboard/Sales.php b/app/code/Magento/Backend/Block/Dashboard/Sales.php index d0f056230bcd1..6d7a4d6458a8e 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Sales.php +++ b/app/code/Magento/Backend/Block/Dashboard/Sales.php @@ -15,7 +15,7 @@ class Sales extends \Magento\Backend\Block\Dashboard\Bar /** * @var string */ - protected $_template = 'dashboard/salebar.phtml'; + protected $_template = 'Magento_Backend::dashboard/salebar.phtml'; /** * @var \Magento\Framework\Module\Manager diff --git a/app/code/Magento/Backend/Block/Dashboard/Totals.php b/app/code/Magento/Backend/Block/Dashboard/Totals.php index 96ae6dd636380..4dcda3677584c 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Totals.php +++ b/app/code/Magento/Backend/Block/Dashboard/Totals.php @@ -16,7 +16,7 @@ class Totals extends \Magento\Backend\Block\Dashboard\Bar /** * @var string */ - protected $_template = 'dashboard/totalbar.phtml'; + protected $_template = 'Magento_Backend::dashboard/totalbar.phtml'; /** * @var \Magento\Framework\Module\Manager diff --git a/app/code/Magento/Backend/Block/GlobalSearch.php b/app/code/Magento/Backend/Block/GlobalSearch.php index f4a46283808f4..3cea12fea205c 100644 --- a/app/code/Magento/Backend/Block/GlobalSearch.php +++ b/app/code/Magento/Backend/Block/GlobalSearch.php @@ -31,6 +31,7 @@ public function getWidgetInitOptions() 'filterProperty' => 'name', 'preventClickPropagation' => false, 'minLength' => 2, + 'submitInputOnEnter' => false, ] ]; } diff --git a/app/code/Magento/Backend/Block/Media/Uploader.php b/app/code/Magento/Backend/Block/Media/Uploader.php index 5bad74d8a8be5..eb98808dd644b 100644 --- a/app/code/Magento/Backend/Block/Media/Uploader.php +++ b/app/code/Magento/Backend/Block/Media/Uploader.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Media; use Magento\Framework\App\ObjectManager; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Image\Adapter\UploadConfigInterface; /** * Adminhtml media library uploader @@ -35,24 +38,35 @@ class Uploader extends \Magento\Backend\Block\Widget */ private $jsonEncoder; + /** + * @var UploadConfigInterface + */ + private $imageConfig; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\File\Size $fileSize * @param array $data * @param Json $jsonEncoder + * @param UploadConfigInterface $imageConfig */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\File\Size $fileSize, array $data = [], - Json $jsonEncoder = null + Json $jsonEncoder = null, + UploadConfigInterface $imageConfig = null ) { $this->_fileSizeService = $fileSize; $this->jsonEncoder = $jsonEncoder ?: ObjectManager::getInstance()->get(Json::class); + $this->imageConfig = $imageConfig ?: ObjectManager::getInstance()->get(UploadConfigInterface::class); + parent::__construct($context, $data); } /** + * Initialize block. + * * @return void */ protected function _construct() @@ -90,6 +104,26 @@ public function getFileSizeService() return $this->_fileSizeService; } + /** + * Get Image Upload Maximum Width Config. + * + * @return int + */ + public function getImageUploadMaxWidth() + { + return $this->imageConfig->getMaxWidth(); + } + + /** + * Get Image Upload Maximum Height Config. + * + * @return int + */ + public function getImageUploadMaxHeight() + { + return $this->imageConfig->getMaxHeight(); + } + /** * Prepares layout and set element renderer * diff --git a/app/code/Magento/Backend/Block/Menu.php b/app/code/Magento/Backend/Block/Menu.php index d6bdeb4ea8968..1e2561e2efe05 100644 --- a/app/code/Magento/Backend/Block/Menu.php +++ b/app/code/Magento/Backend/Block/Menu.php @@ -9,12 +9,12 @@ /** * Backend menu block * - * @api - * @method \Magento\Backend\Block\Menu setAdditionalCacheKeyInfo(array $cacheKeyInfo) + * @method $this setAdditionalCacheKeyInfo(array $cacheKeyInfo) * @method array getAdditionalCacheKeyInfo() - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Menu extends \Magento\Backend\Block\Template { @@ -75,7 +75,12 @@ class Menu extends \Magento\Backend\Block\Template private $anchorRenderer; /** - * @param Template\Context $context + * @var \Magento\Framework\App\Route\ConfigInterface + */ + private $routeConfig; + + /** + * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Model\UrlInterface $url * @param \Magento\Backend\Model\Menu\Filter\IteratorFactory $iteratorFactory * @param \Magento\Backend\Model\Auth\Session $authSession @@ -84,6 +89,9 @@ class Menu extends \Magento\Backend\Block\Template * @param array $data * @param MenuItemChecker|null $menuItemChecker * @param AnchorRenderer|null $anchorRenderer + * @param \Magento\Framework\App\Route\ConfigInterface|null $routeConfig + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -94,7 +102,8 @@ public function __construct( \Magento\Framework\Locale\ResolverInterface $localeResolver, array $data = [], MenuItemChecker $menuItemChecker = null, - AnchorRenderer $anchorRenderer = null + AnchorRenderer $anchorRenderer = null, + \Magento\Framework\App\Route\ConfigInterface $routeConfig = null ) { $this->_url = $url; $this->_iteratorFactory = $iteratorFactory; @@ -103,6 +112,9 @@ public function __construct( $this->_localeResolver = $localeResolver; $this->menuItemChecker = $menuItemChecker; $this->anchorRenderer = $anchorRenderer; + $this->routeConfig = $routeConfig ?: + \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\App\Route\ConfigInterface::class); parent::__construct($context, $data); } @@ -130,6 +142,7 @@ protected function _getAnchorLabel($menuItem) /** * Render menu item mouse events + * * @param \Magento\Backend\Model\Menu\Item $menuItem * @return string */ @@ -203,8 +216,9 @@ protected function _afterToHtml($html) */ protected function _callbackSecretKey($match) { + $routeId = $this->routeConfig->getRouteByFrontName($match[1]); return \Magento\Backend\Model\UrlInterface::SECRET_KEY_PARAM_NAME . '/' . $this->_url->getSecretKey( - $match[1], + $routeId ?: $match[1], $match[2], $match[3] ); @@ -342,7 +356,7 @@ protected function _columnBrake($items, $limit) * @param \Magento\Backend\Model\Menu\Item $menuItem * @param int $level * @param int $limit - * @param $id int + * @param int|null $id * @return string HTML code */ protected function _addSubMenu($menuItem, $level, $limit, $id = null) @@ -352,7 +366,7 @@ protected function _addSubMenu($menuItem, $level, $limit, $id = null) return $output; } $output .= '<div class="submenu"' . ($level == 0 && isset($id) ? ' aria-labelledby="' . $id . '"' : '') . '>'; - $colStops = null; + $colStops = []; if ($level == 0 && $limit) { $colStops = $this->_columnBrake($menuItem->getChildren(), $limit); $output .= '<strong class="submenu-title">' . $this->_getAnchorLabel($menuItem) . '</strong>'; @@ -387,7 +401,11 @@ public function renderNavigation($menu, $level = 0, $limit = 0, $colBrakes = []) $itemName = substr($menuId, strrpos($menuId, '::') + 2); $itemClass = str_replace('_', '-', strtolower($itemName)); - if (count($colBrakes) && $colBrakes[$itemPosition]['colbrake'] && $itemPosition != 1) { + if (is_array($colBrakes) + && count($colBrakes) + && $colBrakes[$itemPosition]['colbrake'] + && $itemPosition != 1 + ) { $output .= '</ul></li><li class="column"><ul role="menu">'; } @@ -401,7 +419,7 @@ public function renderNavigation($menu, $level = 0, $limit = 0, $colBrakes = []) $itemPosition++; } - if (count($colBrakes) && $limit) { + if (is_array($colBrakes) && count($colBrakes) && $limit) { $output = '<li class="column"><ul role="menu">' . $output . '</ul></li>'; } diff --git a/app/code/Magento/Backend/Block/Page/Copyright.php b/app/code/Magento/Backend/Block/Page/Copyright.php index 062497d6a8304..a1b61352930b5 100644 --- a/app/code/Magento/Backend/Block/Page/Copyright.php +++ b/app/code/Magento/Backend/Block/Page/Copyright.php @@ -18,5 +18,5 @@ class Copyright extends \Magento\Backend\Block\Template * * @var string */ - protected $_template = 'page/copyright.phtml'; + protected $_template = 'Magento_Backend::page/copyright.phtml'; } diff --git a/app/code/Magento/Backend/Block/Page/Footer.php b/app/code/Magento/Backend/Block/Page/Footer.php index 368869b79e15c..3d1570e5ddfe7 100644 --- a/app/code/Magento/Backend/Block/Page/Footer.php +++ b/app/code/Magento/Backend/Block/Page/Footer.php @@ -17,7 +17,7 @@ class Footer extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'page/footer.phtml'; + protected $_template = 'Magento_Backend::page/footer.phtml'; /** * @var \Magento\Framework\App\ProductMetadataInterface diff --git a/app/code/Magento/Backend/Block/Page/Header.php b/app/code/Magento/Backend/Block/Page/Header.php index b7ed05ce58e95..c2c5f7472b370 100644 --- a/app/code/Magento/Backend/Block/Page/Header.php +++ b/app/code/Magento/Backend/Block/Page/Header.php @@ -18,7 +18,7 @@ class Header extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'page/header.phtml'; + protected $_template = 'Magento_Backend::page/header.phtml'; /** * Backend data diff --git a/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset.php b/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset.php index 2f9b73f0ae037..6fe8416784c2e 100644 --- a/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset.php +++ b/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset.php @@ -25,7 +25,7 @@ class Fieldset extends \Magento\Backend\Block\Template implements RendererInterf /** * @var string */ - protected $_template = 'store/switcher/form/renderer/fieldset.phtml'; + protected $_template = 'Magento_Backend::store/switcher/form/renderer/fieldset.phtml'; /** * Retrieve an element diff --git a/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php b/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php index ddd1f1a9178cd..71d4db6849bd2 100644 --- a/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php +++ b/app/code/Magento/Backend/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php @@ -23,7 +23,7 @@ class Element extends \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Eleme /** * @var string */ - protected $_template = 'store/switcher/form/renderer/fieldset/element.phtml'; + protected $_template = 'Magento_Backend::store/switcher/form/renderer/fieldset/element.phtml'; /** * Retrieve an element diff --git a/app/code/Magento/Backend/Block/System/Store/Delete/Group.php b/app/code/Magento/Backend/Block/System/Store/Delete/Group.php index ae80b56066a6d..e95f3bbf9f8c1 100644 --- a/app/code/Magento/Backend/Block/System/Store/Delete/Group.php +++ b/app/code/Magento/Backend/Block/System/Store/Delete/Group.php @@ -19,7 +19,7 @@ protected function _prepareLayout() { $itemId = $this->getRequest()->getParam('group_id'); - $this->setTemplate('system/store/delete_group.phtml'); + $this->setTemplate('Magento_Backend::system/store/delete_group.phtml'); $this->setAction($this->getUrl('adminhtml/*/deleteGroupPost', ['group_id' => $itemId])); $this->addChild( 'confirm_deletion_button', diff --git a/app/code/Magento/Backend/Block/System/Store/Delete/Website.php b/app/code/Magento/Backend/Block/System/Store/Delete/Website.php index da28a471130cc..82cbb780137b8 100644 --- a/app/code/Magento/Backend/Block/System/Store/Delete/Website.php +++ b/app/code/Magento/Backend/Block/System/Store/Delete/Website.php @@ -19,7 +19,7 @@ protected function _prepareLayout() { $itemId = $this->getRequest()->getParam('website_id'); - $this->setTemplate('system/store/delete_website.phtml'); + $this->setTemplate('Magento_Backend::system/store/delete_website.phtml'); $this->setAction($this->getUrl('adminhtml/*/deleteWebsitePost', ['website_id' => $itemId])); $this->addChild( 'confirm_deletion_button', diff --git a/app/code/Magento/Backend/Block/System/Store/Edit/AbstractForm.php b/app/code/Magento/Backend/Block/System/Store/Edit/AbstractForm.php index f19799d2e4939..034887c67d1ee 100644 --- a/app/code/Magento/Backend/Block/System/Store/Edit/AbstractForm.php +++ b/app/code/Magento/Backend/Block/System/Store/Edit/AbstractForm.php @@ -37,7 +37,7 @@ protected function _prepareForm() ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']] ); - $this->_prepareStoreFieldSet($form); + $this->_prepareStoreFieldset($form); $form->addField( 'store_type', diff --git a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php index 59657f38465d7..3d7154eb20f92 100644 --- a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php +++ b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php @@ -27,6 +27,7 @@ public function render(\Magento\Framework\DataObject $row) $this->getUrl('adminhtml/*/editGroup', ['group_id' => $row->getGroupId()]) . '">' . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . - '</a>'; + '</a><br />' + . '(' . __('Code') . ': ' . $row->getGroupCode() . ')'; } } diff --git a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Store.php b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Store.php index 23b2de683a958..9cfc8bfc52691 100644 --- a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Store.php +++ b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Store.php @@ -27,6 +27,7 @@ public function render(\Magento\Framework\DataObject $row) $this->getUrl('adminhtml/*/editStore', ['store_id' => $row->getStoreId()]) . '">' . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . - '</a>'; + '</a><br />' . + '(' . __('Code') . ': ' . $row->getStoreCode() . ')'; } } diff --git a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Website.php b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Website.php index 913e2c903d20c..487eb4f8acfda 100644 --- a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Website.php +++ b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Website.php @@ -24,6 +24,7 @@ public function render(\Magento\Framework\DataObject $row) $this->getUrl('adminhtml/*/editWebsite', ['website_id' => $row->getWebsiteId()]) . '">' . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . - '</a>'; + '</a><br />' . + '(' . __('Code') . ': ' . $row->getCode() . ')'; } } diff --git a/app/code/Magento/Backend/Block/Template.php b/app/code/Magento/Backend/Block/Template.php index d0f39b54c1492..3ae4451a2592f 100644 --- a/app/code/Magento/Backend/Block/Template.php +++ b/app/code/Magento/Backend/Block/Template.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Backend\Block; /** @@ -17,10 +20,12 @@ * Example: * <block name="my.block" class="Magento\Backend\Block\Template" template="My_Module::template.phtml" > * <arguments> - * <argument name="viewModel" xsi:type="object">My\Module\ViewModel\Custom</argument> + * <argument name="view_model" xsi:type="object">My\Module\ViewModel\Custom</argument> * </arguments> * </block> * + * Your class object can then be accessed by doing $block->getViewModel() + * * @api * @SuppressWarnings(PHPMD.NumberOfChildren) * @since 100.0.2 diff --git a/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php b/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php index 94af9a1d7578f..5a792ddb39132 100644 --- a/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php +++ b/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php @@ -7,6 +7,8 @@ namespace Magento\Backend\Block\Widget\Button; /** + * Button list widget + * * @api * @since 100.0.2 */ @@ -127,12 +129,6 @@ public function getItems() */ public function sortButtons(Item $itemA, Item $itemB) { - $sortOrderA = intval($itemA->getSortOrder()); - $sortOrderB = intval($itemB->getSortOrder()); - - if ($sortOrderA == $sortOrderB) { - return 0; - } - return ($sortOrderA < $sortOrderB) ? -1 : 1; + return (int)$itemA->getSortOrder() <=> (int)$itemB->getSortOrder(); } } diff --git a/app/code/Magento/Backend/Block/Widget/Form.php b/app/code/Magento/Backend/Block/Widget/Form.php index 30221618edbed..59b5cc060cc05 100644 --- a/app/code/Magento/Backend/Block/Widget/Form.php +++ b/app/code/Magento/Backend/Block/Widget/Form.php @@ -3,8 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block\Widget; +use Magento\Framework\App\ObjectManager; + /** * Backend form widget * @@ -27,13 +30,23 @@ class Form extends \Magento\Backend\Block\Widget */ protected $_template = 'Magento_Backend::widget/form.phtml'; + /** @var Form\Element\ElementCreator */ + private $creator; + /** + * Constructs form + * * @param \Magento\Backend\Block\Template\Context $context * @param array $data + * @param Form\Element\ElementCreator|null $creator */ - public function __construct(\Magento\Backend\Block\Template\Context $context, array $data = []) - { + public function __construct( + \Magento\Backend\Block\Template\Context $context, + array $data = [], + Form\Element\ElementCreator $creator = null + ) { parent::__construct($context, $data); + $this->creator = $creator ?: ObjectManager::getInstance()->get(Form\Element\ElementCreator::class); } /** @@ -148,6 +161,7 @@ protected function _beforeToHtml() /** * Initialize form fields values + * * Method will be called after prepareForm and can be used for field values initialization * * @return $this @@ -173,32 +187,11 @@ protected function _setFieldset($attributes, $fieldset, $exclude = []) if (!$this->_isAttributeVisible($attribute)) { continue; } - if (($inputType = $attribute->getFrontend()->getInputType()) && !in_array( - $attribute->getAttributeCode(), - $exclude - ) && ('media_image' != $inputType || $attribute->getAttributeCode() == 'image') + if (($inputType = $attribute->getFrontend()->getInputType()) + && !in_array($attribute->getAttributeCode(), $exclude) + && ('media_image' !== $inputType || $attribute->getAttributeCode() == 'image') ) { - $fieldType = $inputType; - $rendererClass = $attribute->getFrontend()->getInputRendererClass(); - if (!empty($rendererClass)) { - $fieldType = $inputType . '_' . $attribute->getAttributeCode(); - $fieldset->addType($fieldType, $rendererClass); - } - - $element = $fieldset->addField( - $attribute->getAttributeCode(), - $fieldType, - [ - 'name' => $attribute->getAttributeCode(), - 'label' => $attribute->getFrontend()->getLocalizedLabel(), - 'class' => $attribute->getFrontend()->getClass(), - 'required' => $attribute->getIsRequired(), - 'note' => $attribute->getNote() - ] - )->setEntityAttribute( - $attribute - ); - + $element = $this->creator->create($fieldset, $attribute); $element->setAfterElementHtml($this->_getAdditionalElementHtml($element)); $this->_applyTypeSpecificConfig($inputType, $element, $attribute); diff --git a/app/code/Magento/Backend/Block/Widget/Form/Container.php b/app/code/Magento/Backend/Block/Widget/Form/Container.php index 8b7babc1bb9b6..97116de6db79b 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Container.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Container.php @@ -93,7 +93,7 @@ protected function _construct() 'class' => 'delete', 'onclick' => 'deleteConfirm(\'' . __( 'Are you sure you want to do this?' - ) . '\', \'' . $this->getDeleteUrl() . '\')' + ) . '\', \'' . $this->getDeleteUrl() . '\', {data: {}})' ] ); } 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 723deab1e9f7e..eff49c3b75ab2 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php @@ -124,14 +124,18 @@ protected function _toHtml() if (!$this->_depends) { return ''; } - return '<script> - require(["mage/adminhtml/form"], function(){ - new FormElementDependenceController(' . - $this->_getDependsJson() . - ($this->_configOptions ? ', ' . - $this->_jsonEncoder->encode( - $this->_configOptions - ) : '') . '); });</script>'; + + $params = $this->_getDependsJson(); + + if ($this->_configOptions) { + $params .= ', ' . $this->_jsonEncoder->encode($this->_configOptions); + } + + return "<script> +require(['mage/adminhtml/form'], function(){ + new FormElementDependenceController({$params}); +}); +</script>"; } /** diff --git a/app/code/Magento/Backend/Block/Widget/Form/Element/ElementCreator.php b/app/code/Magento/Backend/Block/Widget/Form/Element/ElementCreator.php new file mode 100644 index 0000000000000..b9cdd259796d0 --- /dev/null +++ b/app/code/Magento/Backend/Block/Widget/Form/Element/ElementCreator.php @@ -0,0 +1,136 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Block\Widget\Form\Element; + +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Data\Form\Element\Fieldset; + +/** + * Class ElementCreator + * + * @deprecated 100.3.0 in favour of UI component implementation + * @package Magento\Backend\Block\Widget\Form\Element + */ +class ElementCreator +{ + /** + * @var array + */ + private $modifiers; + + /** + * ElementCreator constructor. + * + * @param array $modifiers + */ + public function __construct(array $modifiers = []) + { + $this->modifiers = $modifiers; + } + + /** + * Creates element + * + * @param Fieldset $fieldset + * @param Attribute $attribute + * + * @return AbstractElement + */ + public function create(Fieldset $fieldset, Attribute $attribute): AbstractElement + { + $config = $this->getElementConfig($attribute); + + if (!empty($config['rendererClass'])) { + $fieldType = $config['inputType'] . '_' . $attribute->getAttributeCode(); + $fieldset->addType($fieldType, $config['rendererClass']); + } + + return $fieldset + ->addField($config['attribute_code'], $config['inputType'], $config) + ->setEntityAttribute($attribute); + } + + /** + * Returns element config + * + * @param Attribute $attribute + * @return array + */ + private function getElementConfig(Attribute $attribute): array + { + $defaultConfig = $this->createDefaultConfig($attribute); + $config = $this->modifyConfig($defaultConfig); + + $config['label'] = __($config['label']); + + return $config; + } + + /** + * Returns default config + * + * @param Attribute $attribute + * @return array + */ + private function createDefaultConfig(Attribute $attribute): array + { + return [ + 'inputType' => $attribute->getFrontend()->getInputType(), + 'rendererClass' => $attribute->getFrontend()->getInputRendererClass(), + 'attribute_code' => $attribute->getAttributeCode(), + 'name' => $attribute->getAttributeCode(), + 'label' => $attribute->getFrontend()->getLabel(), + 'class' => $attribute->getFrontend()->getClass(), + 'required' => $attribute->getIsRequired(), + 'note' => $attribute->getNote(), + ]; + } + + /** + * Modify config + * + * @param array $config + * @return array + */ + private function modifyConfig(array $config): array + { + if ($this->isModified($config['attribute_code'])) { + return $this->applyModifier($config); + } + return $config; + } + + /** + * Returns bool if attribute need to modify + * + * @param string $attribute_code + * @return bool + */ + private function isModified($attribute_code): bool + { + return isset($this->modifiers[$attribute_code]); + } + + /** + * Apply modifier to config + * + * @param array $config + * @return array + */ + private function applyModifier(array $config): array + { + $modifiedConfig = $this->modifiers[$config['attribute_code']]; + foreach (array_keys($config) as $key) { + if (isset($modifiedConfig[$key])) { + $config[$key] = $modifiedConfig[$key]; + } + } + return $config; + } +} diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Date.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Date.php index 40a5d92c56b6f..632603d389d21 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Date.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Date.php @@ -127,7 +127,7 @@ public function getHtml() /** * @param string|null $index - * @return string + * @return array|string|int|float|null */ public function getEscapedValue($index = null) { @@ -138,6 +138,11 @@ public function getEscapedValue($index = null) $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT) ); } + + if (is_string($value)) { + return $this->escapeHtml($value); + } + return $value; } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php index 96b3471db845e..1d8d658267020 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php @@ -26,7 +26,6 @@ public function getValue($index = null) { if ($index) { if ($data = $this->getData('value', 'orig_' . $index)) { - // date('Y-m-d', strtotime($data)); return $data; } return null; @@ -140,8 +139,8 @@ public function getHtml() /** * Return escaped value for calendar * - * @param string $index - * @return string + * @param string|null $index + * @return array|string|int|float|null */ public function getEscapedValue($index = null) { @@ -150,6 +149,11 @@ public function getEscapedValue($index = null) if ($value instanceof \DateTimeInterface) { return $this->_localeDate->formatDateTime($value); } + + if (is_string($value)) { + return $this->escapeHtml($value); + } + return $value; } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Radio.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Radio.php index 2cbe264c5f396..479a2b6b20293 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Radio.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Radio.php @@ -31,8 +31,7 @@ public function getCondition() { if ($this->getValue()) { return $this->getColumn()->getValue(); - } else { - return [['neq' => $this->getColumn()->getValue()], ['is' => new \Zend_Db_Expr('NULL')]]; } + return [['neq' => $this->getColumn()->getValue()], ['is' => new \Zend_Db_Expr('NULL')]]; } } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php index ff0399e4f507f..03566bce3fc34 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Currency.php @@ -68,10 +68,7 @@ public function __construct( $this->_storeManager = $storeManager; $this->_currencyLocator = $currencyLocator; $this->_localeCurrency = $localeCurrency; - $defaultBaseCurrencyCode = $this->_scopeConfig->getValue( - \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, - 'default' - ); + $defaultBaseCurrencyCode = $currencyLocator->getDefaultCurrency($this->_request); $this->_defaultBaseCurrency = $currencyFactory->create()->load($defaultBaseCurrencyCode); } @@ -85,7 +82,7 @@ public function render(\Magento\Framework\DataObject $row) { if ($data = (string)$this->_getValue($row)) { $currency_code = $this->_getCurrencyCode($row); - $data = floatval($data) * $this->_getRate($row); + $data = (float)$data * $this->_getRate($row); $sign = (bool)(int)$this->getColumn()->getShowNumberSign() && $data > 0 ? '+' : ''; $data = sprintf("%f", $data); $data = $this->_localeCurrency->getCurrency($currency_code)->toCurrency($data); @@ -121,10 +118,10 @@ protected function _getCurrencyCode($row) protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { - return floatval($rate); + return (float)$rate; } if ($rate = $row->getData($this->getColumn()->getRateField())) { - return floatval($rate); + return (float)$rate; } return $this->_defaultBaseCurrency->getRate($this->_getCurrencyCode($row)); } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php index 320713f8b57c4..a611e91f32f00 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php @@ -65,7 +65,7 @@ public function render(\Magento\Framework\DataObject $row) */ protected function _getCheckboxHtml($value, $checked) { - $id = 'id_' . rand(0, 999); + $id = 'id_' . random_int(0, 999); $html = '<label class="data-grid-checkbox-cell-inner" for="'. $id .'">'; $html .= '<input type="checkbox" name="' . $this->getColumn()->getName() . '" '; $html .= 'id="' . $id . '" data-role="select-row"'; diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php index e4300c63485f5..9da23af83f036 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Price.php @@ -60,7 +60,7 @@ public function render(\Magento\Framework\DataObject $row) return $data; } - $data = floatval($data) * $this->_getRate($row); + $data = (float)$data * $this->_getRate($row); $data = sprintf("%f", $data); $data = $this->_localeCurrency->getCurrency($currencyCode)->toCurrency($data); return $data; @@ -94,10 +94,10 @@ protected function _getCurrencyCode($row) protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { - return floatval($rate); + return (float)$rate; } if ($rate = $row->getData($this->getColumn()->getRateField())) { - return floatval($rate); + return (float)$rate; } return 1; } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/ColumnSet.php b/app/code/Magento/Backend/Block/Widget/Grid/ColumnSet.php index 1a6f937f1171d..9443ffefbc0df 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/ColumnSet.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/ColumnSet.php @@ -258,7 +258,8 @@ public function getRowUrl($item) */ public function getMultipleRows($item) { - return $item->getChildren(); + $children = $item->getChildren(); + return $children ?: []; } /** diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php index d9b00d2ba2503..662cbedaed8db 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php @@ -3,8 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block\Widget\Grid; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Json\EncoderInterface; + /** * Grid widget massaction default block * @@ -14,4 +21,72 @@ */ class Massaction extends \Magento\Backend\Block\Widget\Grid\Massaction\AbstractMassaction { + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * Map bind item id to a particular acl type + * itemId => acl + * + * @var array + */ + private $restrictions = [ + 'enable' => 'Magento_Backend::toggling_cache_type', + 'disable' => 'Magento_Backend::toggling_cache_type', + 'refresh' => 'Magento_Backend::refresh_cache_type', + ]; + + /** + * Massaction constructor. + * + * @param Context $context + * @param EncoderInterface $jsonEncoder + * @param array $data + * @param AuthorizationInterface $authorization + */ + public function __construct( + Context $context, + EncoderInterface $jsonEncoder, + array $data = [], + AuthorizationInterface $authorization = null + ) { + $this->authorization = $authorization ?: ObjectManager::getInstance()->get(AuthorizationInterface::class); + + parent::__construct($context, $jsonEncoder, $data); + } + + /** + * {@inheritdoc} + * + * @param string $itemId + * @param array|DataObject $item + * + * @return $this + */ + public function addItem($itemId, $item) + { + if (!$this->isRestricted($itemId)) { + parent::addItem($itemId, $item); + } + + return $this; + } + + /** + * Check if access to action restricted + * + * @param string $itemId + * + * @return bool + */ + private function isRestricted(string $itemId): bool + { + if (!key_exists($itemId, $this->restrictions)) { + return false; + } + + return !$this->authorization->isAllowed($this->restrictions[$itemId]); + } } 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 8252ed1a1e2f8..185b1116b8f67 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block\Widget\Grid\Massaction; use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker; @@ -57,7 +58,7 @@ protected function _construct() { parent::_construct(); - $this->setErrorText($this->escapeHtml(__('Please select items.'))); + $this->setErrorText($this->escapeHtml(__('An item needs to be selected. Select and try again.'))); if (null !== $this->getOptions()) { foreach ($this->getOptions() as $optionId => $option) { @@ -222,9 +223,8 @@ public function getSelectedJson() if ($selected = $this->getRequest()->getParam($this->getFormFieldNameInternal())) { $selected = explode(',', $selected); return join(',', $selected); - } else { - return ''; } + return ''; } /** @@ -237,9 +237,8 @@ public function getSelected() if ($selected = $this->getRequest()->getParam($this->getFormFieldNameInternal())) { $selected = explode(',', $selected); return $selected; - } else { - return []; } + return []; } /** @@ -278,13 +277,13 @@ public function getGridIdsJson() } /** @var \Magento\Framework\Data\Collection $allIdsCollection */ $allIdsCollection = clone $this->getParentBlock()->getCollection(); - + if ($this->getMassactionIdField()) { $massActionIdField = $this->getMassactionIdField(); } else { $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } - + $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); if (!empty($gridIds)) { return join(",", $gridIds); diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php index 42f5e61bf5fa8..8e0fce2b16cc9 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block\Widget\Grid\Massaction; /** @@ -69,7 +70,7 @@ public function __construct( public function _construct() { parent::_construct(); - $this->setErrorText($this->escapeHtml(__('Please select items.'))); + $this->setErrorText($this->escapeHtml(__('An item needs to be selected. Select and try again.'))); } /** @@ -218,9 +219,8 @@ public function getSelectedJson() if ($selected = $this->getRequest()->getParam($this->getFormFieldNameInternal())) { $selected = explode(',', $selected); return join(',', $selected); - } else { - return ''; } + return ''; } /** @@ -233,9 +233,8 @@ public function getSelected() if ($selected = $this->getRequest()->getParam($this->getFormFieldNameInternal())) { $selected = explode(',', $selected); return $selected; - } else { - return []; } + return []; } /** @@ -275,13 +274,13 @@ public function getGridIdsJson() /** @var \Magento\Framework\Data\Collection $allIdsCollection */ $allIdsCollection = clone $this->getParentBlock()->getCollection(); - + if ($this->getMassactionIdField()) { $massActionIdField = $this->getMassactionIdField(); } else { $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } - + $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); if (!empty($gridIds)) { diff --git a/app/code/Magento/Backend/Block/Widget/Tabs.php b/app/code/Magento/Backend/Block/Widget/Tabs.php index 7f146e19e8c6e..333904e398cf5 100644 --- a/app/code/Magento/Backend/Block/Widget/Tabs.php +++ b/app/code/Magento/Backend/Block/Widget/Tabs.php @@ -117,6 +117,7 @@ public function addTab($tabId, $tab) if (empty($tabId)) { throw new \Exception(__('Please correct the tab configuration and try again. Tab Id should be not empty')); } + if (is_array($tab)) { $this->_tabs[$tabId] = new \Magento\Framework\DataObject($tab); } elseif ($tab instanceof \Magento\Framework\DataObject) { @@ -126,6 +127,7 @@ public function addTab($tabId, $tab) } } elseif (is_string($tab)) { $this->_addTabByName($tab, $tabId); + if (!$this->_tabs[$tabId] instanceof TabInterface) { unset($this->_tabs[$tabId]); return $this; @@ -133,6 +135,7 @@ public function addTab($tabId, $tab) } else { throw new \Exception(__('Please correct the tab configuration and try again.')); } + if ($this->_tabs[$tabId]->getUrl() === null) { $this->_tabs[$tabId]->setUrl('#'); } @@ -143,10 +146,7 @@ public function addTab($tabId, $tab) $this->_tabs[$tabId]->setId($tabId); $this->_tabs[$tabId]->setTabId($tabId); - - if ($this->_activeTab === null) { - $this->_activeTab = $tabId; - } + if (true === $this->_tabs[$tabId]->getActive()) { $this->setActiveTab($tabId); } @@ -235,33 +235,108 @@ protected function _setActiveTab($tabId) */ protected function _beforeToHtml() { + $this->_tabs = $this->reorderTabs(); + if ($activeTab = $this->getRequest()->getParam('active_tab')) { $this->setActiveTab($activeTab); } elseif ($activeTabId = $this->_authSession->getActiveTabId()) { $this->_setActiveTab($activeTabId); } - $_new = []; + if ($this->_activeTab === null && !empty($this->_tabs)) { + /** @var TabInterface $tab */ + $this->_activeTab = (reset($this->_tabs))->getId(); + } + + $this->assign('tabs', $this->_tabs); + return parent::_beforeToHtml(); + } + + /** + * Reorder the tabs. + * + * @return array + */ + private function reorderTabs() + { + $orderByIdentity = []; + $orderByPosition = []; + $position = 100; + + /** + * Set the initial positions for each tab. + * + * @var string $key + * @var TabInterface $tab + */ foreach ($this->_tabs as $key => $tab) { - foreach ($this->_tabs as $k => $t) { - if ($t->getAfter() == $key) { - $_new[$key] = $tab; - $_new[$k] = $t; - } else { - if (!$tab->getAfter() || !in_array($tab->getAfter(), array_keys($this->_tabs))) { - $_new[$key] = $tab; - } - } - } + $tab->setPosition($position); + + $orderByIdentity[$key] = $tab; + $orderByPosition[$position] = $tab; + + $position += 100; } - $this->_tabs = $_new; - unset($_new); + return $this->applyTabsCorrectOrder($orderByPosition, $orderByIdentity); + } - $this->assign('tabs', $this->_tabs); - return parent::_beforeToHtml(); + /** + * @param array $orderByPosition + * @param array $orderByIdentity + * + * @return array + */ + private function applyTabsCorrectOrder(array $orderByPosition, array $orderByIdentity) + { + $positionFactor = 1; + + /** + * Rearrange the positions by using the after tag for each tab. + * + * @var integer $position + * @var TabInterface $tab + */ + foreach ($orderByPosition as $position => $tab) { + if (!$tab->getAfter() || !in_array($tab->getAfter(), array_keys($orderByIdentity))) { + $positionFactor = 1; + continue; + } + + $grandPosition = $orderByIdentity[$tab->getAfter()]->getPosition(); + $newPosition = $grandPosition + $positionFactor; + + unset($orderByPosition[$position]); + $orderByPosition[$newPosition] = $tab; + $tab->setPosition($newPosition); + + $positionFactor++; + } + + return $this->finalTabsSortOrder($orderByPosition); } + /** + * Apply the last sort order to tabs. + * + * @param array $orderByPosition + * + * @return array + */ + private function finalTabsSortOrder(array $orderByPosition) + { + ksort($orderByPosition); + + $ordered = []; + + /** @var TabInterface $tab */ + foreach ($orderByPosition as $tab) { + $ordered[$tab->getId()] = $tab; + } + + return $ordered; + } + /** * @return string */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Auth/DeniedJson.php b/app/code/Magento/Backend/Controller/Adminhtml/Auth/DeniedJson.php index ad4546097768a..23731e29f0df4 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Auth/DeniedJson.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Auth/DeniedJson.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\Auth; -class DeniedJson extends \Magento\Backend\Controller\Adminhtml\Auth +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class DeniedJson extends \Magento\Backend\Controller\Adminhtml\Auth implements HttpGetActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Login.php b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Login.php index b34e4d9c84939..1de77c810f316 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Login.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Login.php @@ -6,11 +6,14 @@ */ namespace Magento\Backend\Controller\Adminhtml\Auth; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGet; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPost; + /** * @api * @since 100.0.2 */ -class Login extends \Magento\Backend\Controller\Adminhtml\Auth +class Login extends \Magento\Backend\Controller\Adminhtml\Auth implements HttpGet, HttpPost { /** * @var \Magento\Framework\View\Result\PageFactory @@ -50,9 +53,8 @@ public function execute() // redirect according to rewrite rule if ($requestUrl != $backendUrl) { return $this->getRedirect($backendUrl); - } else { - return $this->resultPageFactory->create(); } + return $this->resultPageFactory->create(); } /** diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php index 41e32c929287a..d7ad080395e29 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php @@ -6,7 +6,10 @@ */ namespace Magento\Backend\Controller\Adminhtml\Auth; -class Logout extends \Magento\Backend\Controller\Adminhtml\Auth +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGet; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPost; + +class Logout extends \Magento\Backend\Controller\Adminhtml\Auth implements HttpGet, HttpPost { /** * Administrator logout action @@ -16,7 +19,7 @@ class Logout extends \Magento\Backend\Controller\Adminhtml\Auth public function execute() { $this->_auth->logout(); - $this->messageManager->addSuccess(__('You have logged out.')); + $this->messageManager->addSuccessMessage(__('You have logged out.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache.php index daaa2a9aaeced..4fcd5993fb504 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Controller\Adminhtml; use Magento\Backend\App\Action; @@ -73,7 +74,7 @@ protected function _validateTypes(array $types) $allTypes = array_keys($this->_cacheTypeList->getTypes()); $invalidTypes = array_diff($types, $allTypes); if (count($invalidTypes) > 0) { - throw new LocalizedException(__('Specified cache type(s) don\'t exist: %1', join(', ', $invalidTypes))); + throw new LocalizedException(__('These cache type(s) don\'t exist: %1', join(', ', $invalidTypes))); } } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php index 1895bd08c464f..79bc19256d270 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php @@ -6,11 +6,19 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Controller\ResultFactory; -class CleanImages extends \Magento\Backend\Controller\Adminhtml\Cache +class CleanImages extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::flush_catalog_images'; + /** * Clean JS/css files cache * @@ -21,11 +29,11 @@ public function execute() try { $this->_objectManager->create(\Magento\Catalog\Model\Product\Image::class)->clearCache(); $this->_eventManager->dispatch('clean_catalog_images_cache_after'); - $this->messageManager->addSuccess(__('The image cache was cleaned.')); + $this->messageManager->addSuccessMessage(__('The image cache was cleaned.')); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while clearing the image cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while clearing the image cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php index 521cb9806a135..36aca1afcc480 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php @@ -6,11 +6,19 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Controller\ResultFactory; -class CleanMedia extends \Magento\Backend\Controller\Adminhtml\Cache +class CleanMedia extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::flush_js_css'; + /** * Clean JS/css files cache * @@ -21,11 +29,12 @@ public function execute() try { $this->_objectManager->get(\Magento\Framework\View\Asset\MergeService::class)->cleanMergedJsCss(); $this->_eventManager->dispatch('clean_media_cache_after'); - $this->messageManager->addSuccess(__('The JavaScript/CSS cache has been cleaned.')); + $this->messageManager->addSuccessMessage(__('The JavaScript/CSS cache has been cleaned.')); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while clearing the JavaScript/CSS cache.')); + $this->messageManager + ->addExceptionMessage($e, __('An error occurred while clearing the JavaScript/CSS cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php index adfbf7cfe63c8..a3a26c5cf6242 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php @@ -6,10 +6,18 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; -class CleanStaticFiles extends \Magento\Backend\Controller\Adminhtml\Cache +class CleanStaticFiles extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::flush_static_files'; + /** * Clean static files cache * @@ -19,7 +27,7 @@ public function execute() { $this->_objectManager->get(\Magento\Framework\App\State\CleanupFiles::class)->clearMaterializedViewFiles(); $this->_eventManager->dispatch('clean_static_files_cache_after'); - $this->messageManager->addSuccess(__('The static files cache has been cleaned.')); + $this->messageManager->addSuccessMessage(__('The static files cache has been cleaned.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php index 2f9dc9ad6a7a5..daf424d14c55b 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php @@ -6,8 +6,17 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; -class FlushAll extends \Magento\Backend\Controller\Adminhtml\Cache +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class FlushAll extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::flush_cache_storage'; + /** * Flush cache storage * @@ -20,7 +29,7 @@ public function execute() foreach ($this->_cacheFrontendPool as $cacheFrontend) { $cacheFrontend->getBackend()->clean(); } - $this->messageManager->addSuccess(__("You flushed the cache storage.")); + $this->messageManager->addSuccessMessage(__("You flushed the cache storage.")); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php index 0f55a59353d65..f3474bf43872b 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php @@ -6,8 +6,17 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; -class FlushSystem extends \Magento\Backend\Controller\Adminhtml\Cache +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class FlushSystem extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::flush_magento_cache'; + /** * Flush all magento cache * @@ -20,7 +29,7 @@ public function execute() $cacheFrontend->clean(); } $this->_eventManager->dispatch('adminhtml_cache_flush_system'); - $this->messageManager->addSuccess(__("The Magento cache storage has been flushed.")); + $this->messageManager->addSuccessMessage(__("The Magento cache storage has been flushed.")); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/Index.php index 05bd309ca620e..f1e908bb842ee 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\Cache; -class Index extends \Magento\Backend\Controller\Adminhtml\Cache +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Backend\Controller\Adminhtml\Cache implements HttpGetActionInterface { /** * Display cache management grid diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php index 204105852b9f1..03b88ca1d3f47 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php @@ -16,6 +16,13 @@ */ class MassDisable extends \Magento\Backend\Controller\Adminhtml\Cache { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::toggling_cache_type'; + /** * @var State */ @@ -60,12 +67,12 @@ private function disableCache() } if ($updatedTypes > 0) { $this->_cacheState->persist(); - $this->messageManager->addSuccess(__("%1 cache type(s) disabled.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) disabled.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while disabling cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while disabling cache.')); } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php index 32acf47887c44..1b98a00d4bf35 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php @@ -16,6 +16,13 @@ */ class MassEnable extends \Magento\Backend\Controller\Adminhtml\Cache { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::toggling_cache_type'; + /** * @var State */ @@ -59,12 +66,12 @@ private function enableCache() } if ($updatedTypes > 0) { $this->_cacheState->persist(); - $this->messageManager->addSuccess(__("%1 cache type(s) enabled.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) enabled.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while enabling cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while enabling cache.')); } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php index e18aa1555e11b..bde211debcf72 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php @@ -11,6 +11,13 @@ class MassRefresh extends \Magento\Backend\Controller\Adminhtml\Cache { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Backend::refresh_cache_type'; + /** * Mass action for cache refresh * @@ -30,12 +37,12 @@ public function execute() $updatedTypes++; } if ($updatedTypes > 0) { - $this->messageManager->addSuccess(__("%1 cache type(s) refreshed.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) refreshed.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while refreshing cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while refreshing cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Index.php index d8c52f6c50bba..decca6837fa00 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Index.php @@ -6,7 +6,11 @@ */ namespace Magento\Backend\Controller\Adminhtml\Dashboard; -class Index extends \Magento\Backend\Controller\Adminhtml\Dashboard +use Magento\Backend\Controller\Adminhtml\Dashboard as DashboardAction; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Index extends DashboardAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php index f831fa67f4bb0..c10d1a77997b7 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php @@ -34,9 +34,9 @@ public function execute() foreach ($collectionsNames as $collectionName) { $this->_objectManager->create($collectionName)->aggregate(); } - $this->messageManager->addSuccess(__('We updated lifetime statistic.')); + $this->messageManager->addSuccessMessage(__('We updated lifetime statistic.')); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t refresh lifetime statistics.')); + $this->messageManager->addErrorMessage(__('We can\'t refresh lifetime statistics.')); $this->logger->critical($e); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Index/GlobalSearch.php b/app/code/Magento/Backend/Controller/Adminhtml/Index/GlobalSearch.php index 9ca4021d08356..37f3064aeaa38 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Index/GlobalSearch.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Index/GlobalSearch.php @@ -6,11 +6,15 @@ */ namespace Magento\Backend\Controller\Adminhtml\Index; +use Magento\Backend\Controller\Adminhtml\Index as IndexAction; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + /** * @api * @since 100.0.2 */ -class GlobalSearch extends \Magento\Backend\Controller\Adminhtml\Index +class GlobalSearch extends IndexAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Index/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/Index/Index.php index a5c71fb2dbc5c..afb1b4573271d 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Index/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Index/Index.php @@ -6,7 +6,10 @@ */ namespace Magento\Backend\Controller\Adminhtml\Index; -class Index extends \Magento\Backend\Controller\Adminhtml\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGet; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPost; + +class Index extends \Magento\Backend\Controller\Adminhtml\Index implements HttpGet, HttpPost { /** * Admin area entry point diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Noroute/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/Noroute/Index.php index e8251b5be6030..e84987d8e1d70 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Noroute/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Noroute/Index.php @@ -6,6 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\Noroute; +/** + * @SuppressWarnings(PHPMD.AllPurposeAction) + */ class Index extends \Magento\Backend\App\Action { /** @@ -34,7 +37,7 @@ public function execute() { /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); - $resultPage->setStatusHeader(404, '1.1', 'Forbidden'); + $resultPage->setStatusHeader(404, '1.1', 'Not Found'); $resultPage->setHeader('Status', '404 File not found'); $resultPage->addHandle('adminhtml_noroute'); return $resultPage; diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Index.php index 54771bfdc1a7d..648f1be86f56c 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Account; -class Index extends \Magento\Backend\Controller\Adminhtml\System\Account +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Backend\Controller\Adminhtml\System\Account implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php index c9bce1cbf3888..d95b0541c2c76 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php @@ -32,9 +32,8 @@ private function getSecurityCookie() { if (!($this->securityCookie instanceof SecurityCookie)) { return \Magento\Framework\App\ObjectManager::getInstance()->get(SecurityCookie::class); - } else { - return $this->securityCookie; } + return $this->securityCookie; } /** @@ -54,9 +53,9 @@ public function execute() $user = $this->_objectManager->create(\Magento\User\Model\User::class)->load($userId); $user->setId($userId) - ->setUsername($this->getRequest()->getParam('username', false)) - ->setFirstname($this->getRequest()->getParam('firstname', false)) - ->setLastname($this->getRequest()->getParam('lastname', false)) + ->setUserName($this->getRequest()->getParam('username', false)) + ->setFirstName($this->getRequest()->getParam('firstname', false)) + ->setLastName($this->getRequest()->getParam('lastname', false)) ->setEmail(strtolower($this->getRequest()->getParam('email', false))); if ($this->_objectManager->get(\Magento\Framework\Validator\Locale::class)->isValid($interfaceLocale)) { @@ -77,12 +76,12 @@ public function execute() $errors = $user->validate(); if ($errors !== true && !empty($errors)) { foreach ($errors as $error) { - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); } } else { $user->save(); $user->sendNotificationEmailsIfRequired(); - $this->messageManager->addSuccess(__('You saved the account.')); + $this->messageManager->addSuccessMessage(__('You saved the account.')); } } catch (UserLockedException $e) { $this->_auth->logout(); @@ -92,12 +91,12 @@ public function execute() } catch (ValidatorException $e) { $this->messageManager->addMessages($e->getMessages()); if ($e->getMessage()) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('An error occurred while saving account.')); + $this->messageManager->addErrorMessage(__('An error occurred while saving account.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php index 76402169f269e..21f28188cf874 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php @@ -19,11 +19,11 @@ public function execute() try { $design->delete(); - $this->messageManager->addSuccess(__('You deleted the design change.')); + $this->messageManager->addSuccessMessage(__('You deleted the design change.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("You can't delete the design change.")); + $this->messageManager->addExceptionMessage($e, __("You can't delete the design change.")); } } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Index.php index 30b26f2294193..c6a05b5a71d0c 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Design; -class Index extends \Magento\Backend\Controller\Adminhtml\System\Design +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Backend\Controller\Adminhtml\System\Design implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php index 1f478604ced7d..0228b48f7f11e 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php @@ -50,9 +50,9 @@ public function execute() try { $design->save(); $this->_eventManager->dispatch('theme_save_after'); - $this->messageManager->addSuccess(__('You saved the design change.')); + $this->messageManager->addSuccessMessage(__('You saved the design change.')); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setDesignData($data); return $resultRedirect->setPath('adminhtml/*/', ['id' => $design->getId()]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php index 4fbae6abb423a..0beeb5168b6d1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php @@ -103,12 +103,12 @@ protected function _backupDatabase() ->setType('db') ->setPath($filesystem->getDirectoryRead(DirectoryList::VAR_DIR)->getAbsolutePath('backups')); $backupDb->createBackup($backup); - $this->messageManager->addSuccess(__('The database was backed up.')); + $this->messageManager->addSuccessMessage(__('The database was backed up.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return false; } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('We can\'t create a backup right now. Please try again later.') ); @@ -125,7 +125,7 @@ protected function _backupDatabase() */ protected function _addDeletionNotice($typeTitle) { - $this->messageManager->addNotice( + $this->messageManager->addNoticeMessage( __( 'Deleting a %1 will not delete the information associated with the %1 (e.g. categories, products, etc.)' . ', but the %1 will not be able to be restored. It is suggested that you create a database backup ' diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php index 925ae4c69ee8e..4e323be709ae1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php @@ -15,13 +15,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Group::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php index b6fbd88c7669c..49c327060dae5 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php @@ -1,16 +1,20 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\Controller\Adminhtml\System\Store; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class DeleteGroupPost extends \Magento\Backend\Controller\Adminhtml\System\Store +/** + * Delete store. + */ +class DeleteGroupPost extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpPostActionInterface { /** + * @inheritDoc * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() @@ -21,11 +25,11 @@ public function execute() $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Group::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $model->getId()]); } @@ -35,12 +39,12 @@ public function execute() try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the store.')); + $this->messageManager->addSuccessMessage(__('You deleted the store.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the store. Please try again later.')); + $this->messageManager->addExceptionMessage($e, __('Unable to delete the store. Please try again later.')); } return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php index b31de6cacc5ff..c340b1ec53aa5 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php @@ -15,13 +15,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Store::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store view cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store view cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php index ac470238e588f..7999012b43594 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php @@ -1,14 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\Controller\Adminhtml\System\Store; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class DeleteStorePost extends \Magento\Backend\Controller\Adminhtml\System\Store +/** + * Delete store view. + */ +class DeleteStorePost extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpPostActionInterface { /** * Delete store view post action @@ -22,11 +25,11 @@ public function execute() /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Store::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store view cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store view cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $model->getId()]); } @@ -37,14 +40,13 @@ public function execute() try { $model->delete(); - $this->_eventManager->dispatch('store_delete', ['store' => $model]); - - $this->messageManager->addSuccess(__('You deleted the store view.')); + $this->messageManager->addSuccessMessage(__('You deleted the store view.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the store view. Please try again later.')); + $this->messageManager + ->addExceptionMessage($e, __('Unable to delete the store view. Please try again later.')); } return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php index 1f2ec4b2ba4b1..9e8664b8ecd11 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; -class DeleteWebsite extends \Magento\Backend\Controller\Adminhtml\System\Store +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class DeleteWebsite extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpGetActionInterface { /** * @return \Magento\Framework\Controller\ResultInterface @@ -15,13 +17,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Website::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This website cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This website cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editWebsite', ['website_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php index c2d24b8c41a8c..3fee1a25c9fe4 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php @@ -6,11 +6,16 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class DeleteWebsitePost extends \Magento\Backend\Controller\Adminhtml\System\Store +/** + * Delete website. + */ +class DeleteWebsitePost extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpPostActionInterface { /** + * @inheritDoc * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() @@ -23,11 +28,11 @@ public function execute() $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!$model) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This website cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This website cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editWebsite', ['website_id' => $model->getId()]); } @@ -37,12 +42,12 @@ public function execute() try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the website.')); + $this->messageManager->addSuccessMessage(__('You deleted the website.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the website. Please try again later.')); + $this->messageManager->addExceptionMessage($e, __('Unable to delete the website. Please try again later.')); } return $redirectResult->setPath('*/*/editWebsite', ['website_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php index cbc068a480865..e5cd43b521fd1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; -class EditStore extends \Magento\Backend\Controller\Adminhtml\System\Store +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class EditStore extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpGetActionInterface { /** * @return \Magento\Framework\Controller\ResultInterface @@ -57,7 +59,7 @@ public function execute() if ($model->getId() || $this->_coreRegistry->registry('store_action') == 'add') { $this->_coreRegistry->register('store_data', $model); if ($this->_coreRegistry->registry('store_action') == 'edit' && $codeBase && !$model->isReadOnly()) { - $this->messageManager->addNotice($codeBase); + $this->messageManager->addNoticeMessage($codeBase); } $resultPage = $this->createPage(); if ($this->_coreRegistry->registry('store_action') == 'add') { @@ -71,7 +73,7 @@ public function execute() )); return $resultPage; } else { - $this->messageManager->addError($notExists); + $this->messageManager->addErrorMessage($notExists); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*/'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditWebsite.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditWebsite.php index bfdf7cdbeb8fb..74ed7951e6214 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditWebsite.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditWebsite.php @@ -6,7 +6,9 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; -class EditWebsite extends \Magento\Backend\Controller\Adminhtml\System\Store +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class EditWebsite extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Forward diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Index.php index b104704f41bdb..54da065c4af91 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Index.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Index.php @@ -6,12 +6,13 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; /** * Class Index returns Stores page */ -class Index extends \Magento\Backend\Controller\Adminhtml\System\Store +class Index extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpGetActionInterface { /** * Returns Stores page diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php index 1d6862a6ff845..b67f1f23f16ba 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php @@ -6,12 +6,14 @@ */ namespace Magento\Backend\Controller\Adminhtml\System\Store; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + /** * Class Save * * Save controller for system entities such as: Store, StoreGroup, Website */ -class Save extends \Magento\Backend\Controller\Adminhtml\System\Store +class Save extends \Magento\Backend\Controller\Adminhtml\System\Store implements HttpPostActionInterface { /** * Process Website model save @@ -32,7 +34,7 @@ private function processWebsiteSave($postData) } $websiteModel->save(); - $this->messageManager->addSuccess(__('You saved the website.')); + $this->messageManager->addSuccessMessage(__('You saved the website.')); return $postData; } @@ -46,7 +48,6 @@ private function processWebsiteSave($postData) */ private function processStoreSave($postData) { - $eventName = 'store_edit'; /** @var \Magento\Store\Model\Store $storeModel */ $storeModel = $this->_objectManager->create(\Magento\Store\Model\Store::class); $postData['store']['name'] = $this->filterManager->removeTags($postData['store']['name']); @@ -56,7 +57,6 @@ private function processStoreSave($postData) $storeModel->setData($postData['store']); if ($postData['store']['store_id'] == '') { $storeModel->setId(null); - $eventName = 'store_add'; } $groupModel = $this->_objectManager->create( \Magento\Store\Model\Group::class @@ -70,9 +70,7 @@ private function processStoreSave($postData) ); } $storeModel->save(); - $this->_objectManager->get(\Magento\Store\Model\StoreManager::class)->reinitStores(); - $this->_eventManager->dispatch($eventName, ['store' => $storeModel]); - $this->messageManager->addSuccess(__('You saved the store view.')); + $this->messageManager->addSuccessMessage(__('You saved the store view.')); return $postData; } @@ -102,8 +100,7 @@ private function processGroupSave($postData) ); } $groupModel->save(); - $this->_eventManager->dispatch('store_group_save', ['group' => $groupModel]); - $this->messageManager->addSuccess(__('You saved the store.')); + $this->messageManager->addSuccessMessage(__('You saved the store.')); return $postData; } @@ -139,10 +136,10 @@ public function execute() $redirectResult->setPath('adminhtml/*/'); return $redirectResult; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_getSession()->setPostData($postData); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while saving. Please review the error log.') ); diff --git a/app/code/Magento/Backend/Helper/Dashboard/Order.php b/app/code/Magento/Backend/Helper/Dashboard/Order.php index 9fc2c2cdb4e6f..c19c28b6e33eb 100644 --- a/app/code/Magento/Backend/Helper/Dashboard/Order.php +++ b/app/code/Magento/Backend/Helper/Dashboard/Order.php @@ -13,7 +13,7 @@ * @api * @since 100.0.2 */ -class Order extends \Magento\Backend\Helper\Dashboard\AbstractDashboard +class Order extends AbstractDashboard { /** * @var \Magento\Reports\Model\ResourceModel\Order\Collection @@ -29,32 +29,25 @@ class Order extends \Magento\Backend\Helper\Dashboard\AbstractDashboard /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection + * @param \Magento\Store\Model\StoreManagerInterface $storeManager */ public function __construct( \Magento\Framework\App\Helper\Context $context, - \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection + \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection, + \Magento\Store\Model\StoreManagerInterface $storeManager = null ) { $this->_orderCollection = $orderCollection; - parent::__construct($context); - } + $this->_storeManager = $storeManager ?: ObjectManager::getInstance() + ->get(\Magento\Store\Model\StoreManagerInterface::class); - /** - * The getter function to get the new StoreManager dependency - * - * @return \Magento\Store\Model\StoreManagerInterface - * - * @deprecated 100.1.0 - */ - private function getStoreManager() - { - if ($this->_storeManager === null) { - $this->_storeManager = ObjectManager::getInstance()->get(\Magento\Store\Model\StoreManagerInterface::class); - } - return $this->_storeManager; + parent::__construct($context); } /** * @return void + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _initCollection() { @@ -65,15 +58,15 @@ protected function _initCollection() if ($this->getParam('store')) { $this->_collection->addFieldToFilter('store_id', $this->getParam('store')); } elseif ($this->getParam('website')) { - $storeIds = $this->getStoreManager()->getWebsite($this->getParam('website'))->getStoreIds(); + $storeIds = $this->_storeManager->getWebsite($this->getParam('website'))->getStoreIds(); $this->_collection->addFieldToFilter('store_id', ['in' => implode(',', $storeIds)]); } elseif ($this->getParam('group')) { - $storeIds = $this->getStoreManager()->getGroup($this->getParam('group'))->getStoreIds(); + $storeIds = $this->_storeManager->getGroup($this->getParam('group'))->getStoreIds(); $this->_collection->addFieldToFilter('store_id', ['in' => implode(',', $storeIds)]); } elseif (!$this->_collection->isLive()) { $this->_collection->addFieldToFilter( 'store_id', - ['eq' => $this->getStoreManager()->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId()] + ['eq' => $this->_storeManager->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId()] ); } $this->_collection->load(); diff --git a/app/code/Magento/Backend/Model/Auth.php b/app/code/Magento/Backend/Model/Auth.php index 08921d1615f87..02bf64fef07ed 100644 --- a/app/code/Magento/Backend/Model/Auth.php +++ b/app/code/Magento/Backend/Model/Auth.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Model; use Magento\Framework\Exception\AuthenticationException; @@ -148,7 +149,12 @@ public function getCredentialStorage() public function login($username, $password) { if (empty($username) || empty($password)) { - self::throwException(__('You did not sign in correctly or your account is temporarily disabled.')); + self::throwException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); } try { @@ -165,7 +171,12 @@ public function login($username, $password) } if (!$this->getAuthStorage()->getUser()) { - self::throwException(__('You did not sign in correctly or your account is temporarily disabled.')); + self::throwException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); } } catch (PluginAuthenticationException $e) { $this->_eventManager->dispatch( @@ -179,7 +190,10 @@ public function login($username, $password) ['user_name' => $username, 'exception' => $e] ); self::throwException( - __($e->getMessage()? : 'You did not sign in correctly or your account is temporarily disabled.') + __( + $e->getMessage()? : 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) ); } } @@ -215,7 +229,7 @@ public function isLoggedIn() public static function throwException(Phrase $msg = null) { if ($msg === null) { - $msg = __('Authentication error occurred.'); + $msg = __('An authentication error occurred. Verify and try again.'); } throw new AuthenticationException($msg); } diff --git a/app/code/Magento/Backend/Model/Auth/StorageInterface.php b/app/code/Magento/Backend/Model/Auth/StorageInterface.php index 52b2b089c71e1..e643165a93317 100644 --- a/app/code/Magento/Backend/Model/Auth/StorageInterface.php +++ b/app/code/Magento/Backend/Model/Auth/StorageInterface.php @@ -23,7 +23,7 @@ interface StorageInterface public function processLogin(); /** - * Perform login specific actions + * Perform logout specific actions * * @return $this * @abstract diff --git a/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php b/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php index 09f33abd0d44d..f6d08883d7a6f 100644 --- a/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php +++ b/app/code/Magento/Backend/Model/Config/SessionLifetime/BackendModel.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Model\Config\SessionLifetime; use Magento\Framework\App\Config\Value; @@ -15,25 +16,31 @@ */ class BackendModel extends Value { - /** Maximum dmin session lifetime; 1 year*/ + /** Maximum admin session lifetime; 1 year*/ const MAX_LIFETIME = 31536000; /** Minimum admin session lifetime */ const MIN_LIFETIME = 60; /** + * Processing object before save data + * * @since 100.1.0 + * @throws LocalizedException */ public function beforeSave() { - $value = (int) $this->getValue(); + $value = (int)$this->getValue(); if ($value > self::MAX_LIFETIME) { throw new LocalizedException( - __('Admin session lifetime must be less than or equal to 31536000 seconds (one year)') + __( + 'The Admin session lifetime is invalid. ' + . 'Set the lifetime to 31536000 seconds (one year) or shorter and try again.' + ) ); } elseif ($value < self::MIN_LIFETIME) { throw new LocalizedException( - __('Admin session lifetime must be greater than or equal to 60 seconds') + __('The Admin session lifetime is invalid. Set the lifetime to 60 seconds or longer and try again.') ); } return parent::beforeSave(); diff --git a/app/code/Magento/Backend/Model/Menu.php b/app/code/Magento/Backend/Model/Menu.php index 22110cf52de2b..0246346ec4d97 100644 --- a/app/code/Magento/Backend/Model/Menu.php +++ b/app/code/Magento/Backend/Model/Menu.php @@ -83,7 +83,7 @@ public function add(Item $item, $parentId = null, $index = null) } $parentItem->getChildren()->add($item, null, $index); } else { - $index = intval($index); + $index = (int) $index; if (!isset($this[$index])) { $this->offsetSet($index, $item); $this->_logger->info( diff --git a/app/code/Magento/Backend/Model/Menu/Builder.php b/app/code/Magento/Backend/Model/Menu/Builder.php index ae572deab53d9..1c6e900bc5cfc 100644 --- a/app/code/Magento/Backend/Model/Menu/Builder.php +++ b/app/code/Magento/Backend/Model/Menu/Builder.php @@ -102,6 +102,6 @@ public function getResult(\Magento\Backend\Model\Menu $menu) */ protected function _getParam($params, $paramName, $defaultValue = null) { - return isset($params[$paramName]) ? $params[$paramName] : $defaultValue; + return $params[$paramName] ?? $defaultValue; } } diff --git a/app/code/Magento/Backend/Model/Menu/Item.php b/app/code/Magento/Backend/Model/Menu/Item.php index 42febe94d0abf..67c6216cbbc06 100644 --- a/app/code/Magento/Backend/Model/Menu/Item.php +++ b/app/code/Magento/Backend/Model/Menu/Item.php @@ -503,12 +503,11 @@ public function populateFromArray(array $data) $this->_tooltip = $this->_getArgument($data, 'toolTip'); $this->_title = $this->_getArgument($data, 'title'); $this->target = $this->_getArgument($data, 'target'); + $this->_submenu = null; if (isset($data['sub_menu'])) { $menu = $this->_menuFactory->create(); $menu->populateFromArray($data['sub_menu']); $this->_submenu = $menu; - } else { - $this->_submenu = null; } } } diff --git a/app/code/Magento/Backend/Model/Menu/Item/Validator.php b/app/code/Magento/Backend/Model/Menu/Item/Validator.php index d79752b296e5f..7b72b355f551d 100644 --- a/app/code/Magento/Backend/Model/Menu/Item/Validator.php +++ b/app/code/Magento/Backend/Model/Menu/Item/Validator.php @@ -74,34 +74,86 @@ public function __construct() * @throws \BadMethodCallException */ public function validate($data) + { + if ($this->checkMenuItemIsRemoved($data)) { + return; + } + + $this->assertContainsRequiredParameters($data); + $this->assertIdentifierIsNotUsed($data['id']); + + foreach ($data as $param => $value) { + $this->validateMenuItemParameter($param, $value); + } + $this->_ids[] = $data['id']; + } + + /** + * Check that menu item is not deleted + * + * @param array $data + * @return bool + */ + private function checkMenuItemIsRemoved($data) + { + return isset($data['id'], $data['removed']) && $data['removed'] === true; + } + + /** + * Check that menu item contains all required data + * @param array $data + * + * @throws \BadMethodCallException + */ + private function assertContainsRequiredParameters($data) { foreach ($this->_required as $param) { if (!isset($data[$param])) { throw new \BadMethodCallException('Missing required param ' . $param); } } + } - if (array_search($data['id'], $this->_ids) !== false) { - throw new \InvalidArgumentException('Item with id ' . $data['id'] . ' already exists'); + /** + * Check that menu item id is not used + * + * @param string $id + * @throws \InvalidArgumentException + */ + private function assertIdentifierIsNotUsed($id) + { + if (array_search($id, $this->_ids) !== false) { + throw new \InvalidArgumentException('Item with id ' . $id . ' already exists'); } + } - foreach ($data as $param => $value) { - if ($data[$param] !== null - && isset( - $this->_validators[$param] - ) && !$this->_validators[$param]->isValid( - $value - ) - ) { - throw new \InvalidArgumentException( - "Param " . $param . " doesn't pass validation: " . implode( - '; ', - $this->_validators[$param]->getMessages() - ) - ); - } + /** + * Validate menu item parameter value + * + * @param string $param + * @param mixed $value + * @throws \InvalidArgumentException + */ + private function validateMenuItemParameter($param, $value) + { + if ($value === null) { + return; } - $this->_ids[] = $data['id']; + if (!isset($this->_validators[$param])) { + return; + } + + $validator = $this->_validators[$param]; + if ($validator->isValid($value)) { + return; + } + + throw new \InvalidArgumentException( + "Param " . $param . " doesn't pass validation: " . implode( + '; ', + $validator->getMessages() + ) + ); } /** diff --git a/app/code/Magento/Backend/Model/Url.php b/app/code/Magento/Backend/Model/Url.php index 48b443fe7ffd3..f199fd0fe7bf1 100644 --- a/app/code/Magento/Backend/Model/Url.php +++ b/app/code/Magento/Backend/Model/Url.php @@ -202,7 +202,7 @@ public function getUrl($routePath = null, $routeParams = null) } $cacheSecretKey = false; - if (is_array($routeParams) && isset($routeParams['_cache_secret_key'])) { + if (isset($routeParams['_cache_secret_key'])) { unset($routeParams['_cache_secret_key']); $cacheSecretKey = true; } @@ -210,25 +210,28 @@ public function getUrl($routePath = null, $routeParams = null) if (!$this->useSecretKey()) { return $result; } + + $this->getRouteParamsResolver()->unsetData('route_params'); $this->_setRoutePath($routePath); + $extraParams = $this->getRouteParamsResolver()->getRouteParams(); $routeName = $this->_getRouteName('*'); $controllerName = $this->_getControllerName(self::DEFAULT_CONTROLLER_NAME); $actionName = $this->_getActionName(self::DEFAULT_ACTION_NAME); - if ($cacheSecretKey) { - $secret = [self::SECRET_KEY_PARAM_NAME => "\${$routeName}/{$controllerName}/{$actionName}\$"]; - } else { - $secret = [ - self::SECRET_KEY_PARAM_NAME => $this->getSecretKey($routeName, $controllerName, $actionName), - ]; - } - if (is_array($routeParams)) { - $routeParams = array_merge($secret, $routeParams); - } else { - $routeParams = $secret; + + if (!isset($routeParams[self::SECRET_KEY_PARAM_NAME])) { + if (!is_array($routeParams)) { + $routeParams = []; + } + $secretKey = $cacheSecretKey + ? "\${$routeName}/{$controllerName}/{$actionName}\$" + : $this->getSecretKey($routeName, $controllerName, $actionName); + $routeParams[self::SECRET_KEY_PARAM_NAME] = $secretKey; } - if (is_array($this->_getRouteParams())) { - $routeParams = array_merge($this->_getRouteParams(), $routeParams); + + if (!empty($extraParams)) { + $routeParams = array_merge($extraParams, $routeParams); } + return parent::getUrl("{$routeName}/{$controllerName}/{$actionName}", $routeParams); } diff --git a/app/code/Magento/Backend/Model/Widget/Grid/Parser.php b/app/code/Magento/Backend/Model/Widget/Grid/Parser.php index e2b77fb471acd..fbed7149aa565 100644 --- a/app/code/Magento/Backend/Model/Widget/Grid/Parser.php +++ b/app/code/Magento/Backend/Model/Widget/Grid/Parser.php @@ -30,8 +30,9 @@ public function parseExpression($expression) $expression = trim($expression); foreach ($this->_operations as $operation) { $splittedExpr = preg_split('/\\' . $operation . '/', $expression, -1, PREG_SPLIT_DELIM_CAPTURE); - if (count($splittedExpr) > 1) { - for ($i = 0; $i < count($splittedExpr); $i++) { + $count = count($splittedExpr); + if ($count > 1) { + for ($i = 0; $i < $count; $i++) { $stack = array_merge($stack, $this->parseExpression($splittedExpr[$i])); if ($i > 0) { $stack[] = $operation; diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml new file mode 100644 index 0000000000000..9ba4430bafe35 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml @@ -0,0 +1,17 @@ +<?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="LoginActionGroup"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> + <fillField userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" selector="{{AdminLoginFormSection.username}}" stepKey="fillUsername"/> + <fillField userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" selector="{{AdminLoginFormSection.password}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml new file mode 100644 index 0000000000000..a7ef237a232b8 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.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="LoginAsAdmin"> + <arguments> + <argument name="adminUser" defaultValue="_ENV"/> + </arguments> + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> + <waitForPageLoad stepKey="waitForAdminLoginPageLoad"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml new file mode 100644 index 0000000000000..a4d922086df34 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml @@ -0,0 +1,14 @@ +<?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="logout"> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml new file mode 100644 index 0000000000000..6f27b03e4df30 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml @@ -0,0 +1,31 @@ +<?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"> + <!-- Action group to delete an item given that items name --> + <!-- Must already be on the admin page containing the grid --> + <actionGroup name="deleteEntitySecondaryGrid"> + <arguments> + <argument name="name" type="string"/> + <argument name="searchInput" type="string"/> + </arguments> + + <!-- search for the name --> + <click stepKey="resetFilters" selector="{{AdminSecondaryGridSection.resetFilters}}"/> + <fillField stepKey="fillIdentifier" selector="{{searchInput}}" userInput="{{name}}"/> + <click stepKey="searchForName" selector="{{AdminSecondaryGridSection.searchButton}}"/> + <click stepKey="clickResult" selector="{{AdminSecondaryGridSection.firstRow}}"/> + <waitForPageLoad stepKey="waitForTaxRateLoad"/> + + <!-- delete the rule --> + <click stepKey="clickDelete" selector="{{AdminStoresMainActionsSection.deleteButton}}"/> + <click stepKey="clickOk" selector="{{AdminConfirmationModalSection.ok}}"/> + <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="deleted"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml new file mode 100644 index 0000000000000..fd353964bae9a --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.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="SortByIdDescendingActionGroup"> + <conditionalClick selector="//div[contains(@data-role, 'grid-wrapper')]/table/thead/tr/th/span[contains(text(), 'ID')]" dependentSelector="//span[contains(text(), 'ID')]/parent::th[not(contains(@class, '_descend'))]/parent::tr/parent::thead/parent::table/parent::div[contains(@data-role, 'grid-wrapper')]" stepKey="clickToAttemptSortByIdDescending" visible="true"/> + <waitForLoadingMaskToDisappear stepKey="waitForFirstIdSortDescendingToFinish" /> + <!-- Conditional Click again in case it goes from default state to ascending on first click --> + <conditionalClick selector="//div[contains(@data-role, 'grid-wrapper')]/table/thead/tr/th/span[contains(text(), 'ID')]" dependentSelector="//span[contains(text(), 'ID')]/parent::th[not(contains(@class, '_descend'))]/parent::tr/parent::thead/parent::table/parent::div[contains(@data-role, 'grid-wrapper')]" stepKey="secondClickToAttemptSortByIdDescending" visible="true"/> + <waitForLoadingMaskToDisappear stepKey="waitForSecondIdSortDescendingToFinish" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml b/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml new file mode 100644 index 0000000000000..016e936977cd0 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.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="backendDataOne" type="backend"> + <data key="backendConfigName">data</data> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/LICENSE.txt b/app/code/Magento/Backend/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/LICENSE.txt rename to app/code/Magento/Backend/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Braintree/LICENSE_AFL.txt b/app/code/Magento/Backend/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Braintree/LICENSE_AFL.txt rename to app/code/Magento/Backend/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml new file mode 100644 index 0000000000000..d1bf3c2cb2ed6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml @@ -0,0 +1,17 @@ +<?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="ConfigurationStoresPage" url="admin/system_config/edit/section/cms/" area="admin" module="Catalog"> + <section name="WYSIWYGOptionsSection"/> + </page> + <page name="WebConfigurationPage" url="admin/system_config/edit/section/web/" area="admin" module="Backend"> + <section name="WYSIWYGOptionsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml new file mode 100644 index 0000000000000..8c258accdf06c --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminDashboardPage" url="admin/dashboard/" area="admin" module="Magento_Backend"> + <section name="AdminMenuSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml new file mode 100644 index 0000000000000..b68b9914186f6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.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="AdminLoginPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminLoginFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml new file mode 100644 index 0000000000000..713199771e824 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.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="AdminLogoutPage" url="admin/auth/logout/" area="admin" module="Magento_Backend"/> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/README.md b/app/code/Magento/Backend/Test/Mftf/README.md new file mode 100644 index 0000000000000..ed8a3a3bc2c49 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Backend Functional Tests + +The Functional Test Module for **Magento Backend** module. diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml new file mode 100644 index 0000000000000..2ec25da461908 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.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="AdminConfirmationModalSection"> + <element name="title" type="text" selector="aside.confirm .modal-title"/> + <element name="message" type="text" selector="aside.confirm .modal-content"/> + <element name="cancel" type="button" selector="aside.confirm .modal-footer button.action-dismiss" timeout="30"/> + <element name="ok" type="button" selector="aside.confirm .modal-footer button.action-accept" timeout="60"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml new file mode 100644 index 0000000000000..cc92e530cf3d4 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.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="AdminGridTableSection"> + <element name="row" type="text" selector="table.data-grid tbody tr[data-role=row]:nth-of-type({{row}})" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml new file mode 100644 index 0000000000000..441ce886f117b --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.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="AdminHeaderSection"> + <element name="pageTitle" type="text" selector=".page-header h1.page-title"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml new file mode 100644 index 0000000000000..3b10fac7bb9dc --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.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="AdminLoginFormSection"> + <element name="username" type="input" selector="#username"/> + <element name="password" type="input" selector="#login"/> + <element name="signIn" type="button" selector=".actions .action-primary" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml new file mode 100644 index 0000000000000..cb164d43a49ff --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.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="AdminMainActionsSection"> + <element name="save" type="button" selector="#save"/> + <element name="delete" type="button" selector="#delete"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml new file mode 100644 index 0000000000000..9e4a6d9219526 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.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="AdminMenuSection"> + <element name="catalog" type="button" selector="#menu-magento-catalog-catalog"/> + <element name="catalogProducts" type="button" selector="#nav li[data-ui-id='menu-magento-catalog-catalog-products']"/> + <element name="customers" type="button" selector="#menu-magento-customer-customer"/> + <element name="content" type="button" selector="#menu-magento-backend-content"/> + <element name="widgets" type="button" selector="#nav li[data-ui-id='menu-magento-widget-cms-widget-instance']"/> + <element name="stores" type="button" selector="#menu-magento-backend-stores"/> + <element name="configuration" type="button" selector="#nav li[data-ui-id='menu-magento-config-system-config']"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml new file mode 100644 index 0000000000000..b1350d5dcc1d7 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.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="AdminMessagesSection"> + <element name="success" type="text" selector="#messages div.message-success"/> + <element name="nthSuccess" type="text" selector=".message.message-success.success:nth-of-type({{n}})>div" parameterized="true"/> + <element name="error" type="text" selector="#messages div.message-error"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml new file mode 100644 index 0000000000000..9051eb747a7a6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml @@ -0,0 +1,19 @@ +<?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="AdminSecondaryGridSection"> + <element name="resetFilters" type="button" selector="[title='Reset Filter']"/> + <element name="taxIdentifierSearch" type="input" selector=".col-code .admin__control-text"/> + <element name="catalogRuleIdentifierSearch" type="input" selector=".col-name .admin__control-text"/> + <element name="searchButton" type="input" selector=".admin__filter-actions [title='Search']"/> + <element name="firstRow" type="block" selector="tr[data-role='row']"/> + </section> +</sections> + diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml new file mode 100644 index 0000000000000..7f0194b7dc347 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -0,0 +1,30 @@ +<?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="AdminLoginTest"> + <annotations> + <features value="Backend"/> + <stories value="Login on the Admin Login page"/> + <title value="Admin should be able to log into the Magento Admin backend"/> + <description value="Admin should be able to log into the Magento Admin backend"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-71572"/> + <group value="example"/> + <group value="login"/> + </annotations> + + <amOnPage url="{{AdminLoginPage.url}}" stepKey="amOnAdminLoginPage"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickOnSignIn"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml new file mode 100644 index 0000000000000..c9a3b8089cc1d --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml @@ -0,0 +1,50 @@ +<?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="AdminMenuNavigationWithSecretKeysTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin should be able to navigate between menu options with secret url keys enabled"/> + <description value="Admin should be able to navigate between menu options with secret url keys enabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-95349"/> + <group value="menu"/> + </annotations> + <before> + <magentoCLI command="config:set admin/security/use_form_key 1" stepKey="enableUrlSecretKeys"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches1"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set admin/security/use_form_key 0" stepKey="disableUrlSecretKeys"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <click selector="{{AdminMenuSection.stores}}" stepKey="clickStoresMenuOption1"/> + <waitForLoadingMaskToDisappear stepKey="waitForStoresMenu1" /> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickStoresConfigurationMenuOption1"/> + <waitForPageLoad stepKey="waitForConfigurationPageLoad1"/> + <seeCurrentUrlMatches regex="~\/admin\/system_config\/~" stepKey="seeCurrentUrlMatchesConfigPath1"/> + + <click selector="{{AdminMenuSection.catalog}}" stepKey="clickCatalogMenuOption"/> + <waitForLoadingMaskToDisappear stepKey="waitForCatalogMenu1" /> + <click selector="{{AdminMenuSection.catalogProducts}}" stepKey="clickCatalogProductsMenuOption"/> + <waitForPageLoad stepKey="waitForProductsPageLoad"/> + <seeCurrentUrlMatches regex="~\/catalog\/product\/~" stepKey="seeCurrentUrlMatchesProductsPath"/> + + <click selector="{{AdminMenuSection.stores}}" stepKey="clickStoresMenuOption2"/> + <waitForLoadingMaskToDisappear stepKey="waitForStoresMenu2" /> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickStoresConfigurationMenuOption2"/> + <waitForPageLoad stepKey="waitForConfigurationPageLoad2"/> + <seeCurrentUrlMatches regex="~\/admin\/system_config\/~" stepKey="seeCurrentUrlMatchesConfigPath2"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/AuthenticationTest.php b/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/AuthenticationTest.php index 7e4c426de9452..88b994a6b93b7 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/AuthenticationTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/AuthenticationTest.php @@ -146,6 +146,9 @@ public function testProcessNotLoggedInUser($isIFrameParam, $isAjaxParam, $isForw $this->assertEquals($expectedResult, $this->plugin->aroundDispatch($subject, $proceed, $request)); } + /** + * @return array + */ public function processNotLoggedInUserDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/MassactionKeyTest.php b/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/MassactionKeyTest.php index 2f808eaf2d1b8..d793a80cdeacf 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/MassactionKeyTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Action/Plugin/MassactionKeyTest.php @@ -74,6 +74,9 @@ public function testBeforeDispatchWhenMassactionPrepareKeyRequestExists($postDat $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } + /** + * @return array + */ public function beforeDispatchDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/App/Action/Stub/ActionStub.php b/app/code/Magento/Backend/Test/Unit/App/Action/Stub/ActionStub.php index 4eff6218961af..2d60bef3f3e8c 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Action/Stub/ActionStub.php +++ b/app/code/Magento/Backend/Test/Unit/App/Action/Stub/ActionStub.php @@ -8,6 +8,9 @@ class ActionStub extends \Magento\Backend\App\Action { + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void + */ public function execute() { // Empty method stub for test diff --git a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php index bc7dce6f20bac..642c6283decae 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php @@ -118,6 +118,9 @@ public function testIsHostBackend($url, $host, $useCustomAdminUrl, $customAdminU $this->assertEquals($this->model->isHostBackend(), $expectedValue); } + /** + * @return array + */ public function hostsDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/App/ConfigTest.php b/app/code/Magento/Backend/Test/Unit/App/ConfigTest.php index 114c57867badf..53640a81e722f 100644 --- a/app/code/Magento/Backend/Test/Unit/App/ConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/ConfigTest.php @@ -70,6 +70,9 @@ public function testIsSetFlag($configPath, $configValue, $expectedResult) $this->assertEquals($expectedResult, $this->model->isSetFlag($configPath)); } + /** + * @return array + */ public function isSetFlagDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/AnchorRendererTest.php b/app/code/Magento/Backend/Test/Unit/Block/AnchorRendererTest.php index f52f4ab337712..eccb08e788a95 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/AnchorRendererTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/AnchorRendererTest.php @@ -141,6 +141,9 @@ public function testRenderAnchorLevelIsNotOne($hasTarget) ); } + /** + * @return array + */ public function targetDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php b/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php index 160cfe609f85f..915b3fe21eaa8 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Cache/AdditionalTest.php @@ -11,7 +11,7 @@ class AdditionalTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Backend\Block\Cache\Additional */ - private $additonalBlock; + private $additionalBlock; /** * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject @@ -39,7 +39,7 @@ protected function setUp() ] ); - $this->additonalBlock = $objectHelper->getObject( + $this->additionalBlock = $objectHelper->getObject( \Magento\Backend\Block\Cache\Additional::class, ['context' => $context] ); @@ -52,7 +52,7 @@ public function testGetCleanImagesUrl() ->method('getUrl') ->with('*/*/cleanImages') ->will($this->returnValue($expectedUrl)); - $this->assertEquals($expectedUrl, $this->additonalBlock->getCleanImagesUrl()); + $this->assertEquals($expectedUrl, $this->additionalBlock->getCleanImagesUrl()); } public function testGetCleanMediaUrl() @@ -62,7 +62,7 @@ public function testGetCleanMediaUrl() ->method('getUrl') ->with('*/*/cleanMedia') ->will($this->returnValue($expectedUrl)); - $this->assertEquals($expectedUrl, $this->additonalBlock->getCleanMediaUrl()); + $this->assertEquals($expectedUrl, $this->additionalBlock->getCleanMediaUrl()); } public function testGetCleanStaticFiles() @@ -72,7 +72,7 @@ public function testGetCleanStaticFiles() ->method('getUrl') ->with('*/*/cleanStaticFiles') ->will($this->returnValue($expectedUrl)); - $this->assertEquals($expectedUrl, $this->additonalBlock->getCleanStaticFilesUrl()); + $this->assertEquals($expectedUrl, $this->additionalBlock->getCleanStaticFilesUrl()); } /** @@ -85,9 +85,12 @@ public function testIsInProductionMode($mode, $expected) $this->appStateMock->expects($this->once()) ->method('getMode') ->willReturn($mode); - $this->assertEquals($expected, $this->additonalBlock->isInProductionMode()); + $this->assertEquals($expected, $this->additionalBlock->isInProductionMode()); } + /** + * @return array + */ public function isInProductionModeDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Cache/PermissionsTest.php b/app/code/Magento/Backend/Test/Unit/Block/Cache/PermissionsTest.php new file mode 100644 index 0000000000000..6975b8ac092ad --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Block/Cache/PermissionsTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Backend\Test\Unit\Block\Cache; + +use Magento\Backend\Block\Cache\Permissions; +use Magento\Framework\Authorization; +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Class PermissionsTest + */ +class PermissionsTest extends TestCase +{ + /** + * @var Permissions + */ + private $permissions; + + /** + * @var AuthorizationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $mockAuthorization; + + /** + * @var ObjectManager + */ + private $objectManager; + + public function setUp() + { + $this->objectManager = new ObjectManager($this); + + $this->mockAuthorization = $this->getMockBuilder(Authorization::class) + ->disableOriginalConstructor() + ->setMethods(['isAllowed']) + ->getMock(); + + $this->permissions = new Permissions($this->mockAuthorization); + } + + public function testHasAccessToFlushCatalogImages() + { + $this->mockAuthorization->expects($this->atLeastOnce()) + ->method('isAllowed') + ->with('Magento_Backend::flush_catalog_images') + ->willReturn(true); + + $this->assertTrue($this->permissions->hasAccessToFlushCatalogImages()); + } + + public function testHasAccessToFlushJsCss() + { + $this->mockAuthorization->expects($this->atLeastOnce()) + ->method('isAllowed') + ->with('Magento_Backend::flush_js_css') + ->willReturn(true); + + $this->assertTrue($this->permissions->hasAccessToFlushJsCss()); + } + + public function testHasAccessToFlushStaticFiles() + { + $this->mockAuthorization->expects($this->atLeastOnce()) + ->method('isAllowed') + ->with('Magento_Backend::flush_static_files') + ->willReturn(true); + + $this->assertTrue($this->permissions->hasAccessToFlushStaticFiles()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Block/MenuItemCheckerTest.php b/app/code/Magento/Backend/Test/Unit/Block/MenuItemCheckerTest.php index a79050faeb84a..aca719b2e65e9 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/MenuItemCheckerTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/MenuItemCheckerTest.php @@ -74,6 +74,9 @@ public function testIsItemActiveLevelNotZero() ); } + /** + * @return array + */ public function dataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/ButtonTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/ButtonTest.php index bcf5d1adbc12b..e64d1a97af4ae 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/ButtonTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/ButtonTest.php @@ -61,6 +61,9 @@ public function testGetAttributesHtml($data, $expect) $this->assertRegExp($expect, $attributes); } + /** + * @return array + */ public function getAttributesHtmlDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DateTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DateTest.php index 355d11b561317..2848c9b23d2b7 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DateTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DateTest.php @@ -30,6 +30,12 @@ class DateTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $localeDateMock; + /** @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject */ + private $escaperMock; + + /** @var \Magento\Backend\Block\Context|\PHPUnit_Framework_MockObject_MockObject */ + private $contextMock; + protected function setUp() { $this->mathRandomMock = $this->getMockBuilder(\Magento\Framework\Math\Random::class) @@ -58,6 +64,17 @@ protected function setUp() ->setMethods([]) ->getMock(); + $this->escaperMock = $this->getMockBuilder(\Magento\Framework\Escaper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock = $this->getMockBuilder(\Magento\Backend\Block\Context::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock->expects($this->once())->method('getEscaper')->willReturn($this->escaperMock); + $this->contextMock->expects($this->once())->method('getLocaleDate')->willReturn($this->localeDateMock); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManagerHelper->getObject( \Magento\Backend\Block\Widget\Grid\Column\Filter\Date::class, @@ -65,7 +82,8 @@ protected function setUp() 'mathRandom' => $this->mathRandomMock, 'localeResolver' => $this->localeResolverMock, 'dateTimeFormatter' => $this->dateTimeFormatterMock, - 'localeDate' => $this->localeDateMock + 'localeDate' => $this->localeDateMock, + 'context' => $this->contextMock, ] ); $this->model->setColumn($this->columnMock); @@ -98,4 +116,16 @@ public function testGetHtmlSuccessfulTimestamp() $this->assertContains('id="' . $uniqueHash . '_from" value="' . $yesterday->getTimestamp(), $output); $this->assertContains('id="' . $uniqueHash . '_to" value="' . $tomorrow->getTimestamp(), $output); } + + public function testGetEscapedValueEscapeString() + { + $value = "\"><img src=x onerror=alert(2) />"; + $array = [ + 'orig_from' => $value, + 'from' => $value, + ]; + $this->model->setValue($array); + $this->escaperMock->expects($this->once())->method('escapeHtml')->with($value); + $this->model->getEscapedValue('from'); + } } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DatetimeTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DatetimeTest.php index 1e692f890a94e..cdb88b6735f15 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DatetimeTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/DatetimeTest.php @@ -30,6 +30,12 @@ class DatetimeTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $localeDateMock; + /** @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject */ + private $escaperMock; + + /** @var \Magento\Backend\Block\Context|\PHPUnit_Framework_MockObject_MockObject */ + private $contextMock; + protected function setUp() { $this->mathRandomMock = $this->getMockBuilder(\Magento\Framework\Math\Random::class) @@ -50,7 +56,7 @@ protected function setUp() $this->columnMock = $this->getMockBuilder(\Magento\Backend\Block\Widget\Grid\Column::class) ->disableOriginalConstructor() - ->setMethods(['getTimezone', 'getHtmlId', 'getId']) + ->setMethods(['getTimezone', 'getHtmlId', 'getId', 'getFilterTime']) ->getMock(); $this->localeDateMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) @@ -58,6 +64,17 @@ protected function setUp() ->setMethods([]) ->getMock(); + $this->escaperMock = $this->getMockBuilder(\Magento\Framework\Escaper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock = $this->getMockBuilder(\Magento\Backend\Block\Context::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock->expects($this->once())->method('getEscaper')->willReturn($this->escaperMock); + $this->contextMock->expects($this->once())->method('getLocaleDate')->willReturn($this->localeDateMock); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManagerHelper->getObject( \Magento\Backend\Block\Widget\Grid\Column\Filter\Datetime::class, @@ -65,7 +82,8 @@ protected function setUp() 'mathRandom' => $this->mathRandomMock, 'localeResolver' => $this->localeResolverMock, 'dateTimeFormatter' => $this->dateTimeFormatterMock, - 'localeDate' => $this->localeDateMock + 'localeDate' => $this->localeDateMock, + 'context' => $this->contextMock, ] ); $this->model->setColumn($this->columnMock); @@ -98,4 +116,17 @@ public function testGetHtmlSuccessfulTimestamp() $this->assertContains('id="' . $uniqueHash . '_from" value="' . $yesterday->getTimestamp(), $output); $this->assertContains('id="' . $uniqueHash . '_to" value="' . $tomorrow->getTimestamp(), $output); } + + public function testGetEscapedValueEscapeString() + { + $value = "\"><img src=x onerror=alert(2) />"; + $array = [ + 'orig_from' => $value, + 'from' => $value, + ]; + $this->model->setValue($array); + $this->escaperMock->expects($this->once())->method('escapeHtml')->with($value); + $this->columnMock->expects($this->once())->method('getFilterTime')->willReturn(true); + $this->model->getEscapedValue('from'); + } } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/Radio/ExtendedTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/Radio/ExtendedTest.php index 35e21d7d194aa..81f104dbb636b 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/Radio/ExtendedTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/Radio/ExtendedTest.php @@ -54,6 +54,9 @@ public function testRender(array $rowData, $expectedResult) $this->assertEquals($expectedResult, $this->_object->render(new \Magento\Framework\DataObject($rowData))); } + /** + * @return array + */ public function renderDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/RadioTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/RadioTest.php index 67ead0ddd8f35..6f838634c6bed 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/RadioTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/RadioTest.php @@ -63,6 +63,9 @@ public function testRender(array $rowData, $expectedResult) $this->assertEquals($expectedResult, $this->_object->render(new \Magento\Framework\DataObject($rowData))); } + /** + * @return array + */ public function renderDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnSetTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnSetTest.php index be171a8ed40bf..df242a4cf6129 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnSetTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnSetTest.php @@ -117,7 +117,7 @@ public function testSetFilterTypePropagatesFilterTypeToColumns() public function testGetRowUrlIfUrlPathNotSet() { - $this->assertEquals('#', $this->_block->getRowUrl(new \StdClass())); + $this->assertEquals('#', $this->_block->getRowUrl(new \stdClass())); } public function testGetRowUrl() diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnTest.php index da13af87b71ea..2e6bed4783e7f 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/ColumnTest.php @@ -86,6 +86,9 @@ public function testGetSortable($value) $this->assertFalse($this->_block->getSortable()); } + /** + * @return array + */ public function getSortableDataProvider() { return ['zero' => ['0'], 'false' => [false], 'null' => [null]]; @@ -351,7 +354,7 @@ public function testSetGetGrid() $this->_block->setFilter('StdClass'); - $grid = new \StdClass(); + $grid = new \stdClass(); $this->_block->setGrid($grid); $this->assertEquals($grid, $this->_block->getGrid()); } @@ -374,6 +377,9 @@ public function testColumnIsGrouped($groupedData, $expected) $this->assertEquals($expected, $block->isGrouped()); } + /** + * @return array + */ public function columnGroupedDataProvider() { return [[[], false], [['grouped' => 0], false], [['grouped' => 1], true]]; diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Massaction/ExtendedTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Massaction/ExtendedTest.php index 4525de1fee542..f81928c4540ba 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Massaction/ExtendedTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Massaction/ExtendedTest.php @@ -152,6 +152,9 @@ public function testGetGridIdsJsonWithUseSelectAll(array $items, $result) $this->assertEquals($result, $this->_block->getGridIdsJson()); } + /** + * @return array + */ public function dataProviderGetGridIdsJsonWithUseSelectAll() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php index bb389a996e1ed..e8143b5f6b43a 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php @@ -10,6 +10,7 @@ namespace Magento\Backend\Test\Unit\Block\Widget\Grid; use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker; +use Magento\Framework\Authorization; class MassactionTest extends \PHPUnit\Framework\TestCase { @@ -43,6 +44,11 @@ class MassactionTest extends \PHPUnit\Framework\TestCase */ protected $_requestMock; + /** + * @var Authorization|\PHPUnit_Framework_MockObject_MockObject + */ + protected $_authorizationMock; + /** * @var VisibilityChecker|\PHPUnit_Framework_MockObject_MockObject */ @@ -86,11 +92,17 @@ protected function setUp() $this->visibilityCheckerMock = $this->getMockBuilder(VisibilityChecker::class) ->getMockForAbstractClass(); + $this->_authorizationMock = $this->getMockBuilder(Authorization::class) + ->disableOriginalConstructor() + ->setMethods(['isAllowed']) + ->getMock(); + $arguments = [ 'layout' => $this->_layoutMock, 'request' => $this->_requestMock, 'urlBuilder' => $this->_urlModelMock, - 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'] + 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'], + 'authorization' => $this->_authorizationMock, ]; $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -145,6 +157,10 @@ public function testItemsProcessing($itemId, $item, $expectedItem) ->method('getUrl') ->willReturnMap($urlReturnValueMap); + $this->_authorizationMock->expects($this->any()) + ->method('isAllowed') + ->willReturn(true); + $this->_block->addItem($itemId, $item); $this->assertEquals(1, $this->_block->getCount()); @@ -184,6 +200,28 @@ public function itemsProcessingDataProvider() "id" => 'test_id2', ] ) + ], + [ + 'enabled', + new \Magento\Framework\DataObject(["label" => "Test Item Enabled", "url" => "*/*/test2"]), + new \Magento\Framework\DataObject( + [ + "label" => "Test Item Enabled", + "url" => "http://localhost/index.php/backend/admin/test/test2", + "id" => 'enabled', + ] + ) + ], + [ + 'refresh', + new \Magento\Framework\DataObject(["label" => "Test Item Refresh", "url" => "*/*/test2"]), + new \Magento\Framework\DataObject( + [ + "label" => "Test Item Refresh", + "url" => "http://localhost/index.php/backend/admin/test/test2", + "id" => 'refresh', + ] + ) ] ]; } @@ -205,6 +243,9 @@ public function testSelected($param, $expectedJson, $expected) $this->assertEquals($expected, $this->_block->getSelected()); } + /** + * @return array + */ public function selectedDataProvider() { return [ @@ -237,7 +278,7 @@ public function testGetGridIdsJsonWithoutUseSelectAll() public function testGetGridIdsJsonWithUseSelectAll(array $items, $result) { $this->_block->setUseSelectAll(true); - + if ($this->_block->getMassactionIdField()) { $massActionIdField = $this->_block->getMassactionIdField(); } else { @@ -290,14 +331,20 @@ public function dataProviderGetGridIdsJsonWithUseSelectAll() * @param int $count * @param bool $withVisibilityChecker * @param bool $isVisible + * @param bool $isAllowed + * * @dataProvider addItemDataProvider */ - public function testAddItem($itemId, $item, $count, $withVisibilityChecker, $isVisible) + public function testAddItem($itemId, $item, $count, $withVisibilityChecker, $isVisible, $isAllowed) { $this->visibilityCheckerMock->expects($this->any()) ->method('isVisible') ->willReturn($isVisible); + $this->_authorizationMock->expects($this->any()) + ->method('isAllowed') + ->willReturn($isAllowed); + if ($withVisibilityChecker) { $item['visible'] = $this->visibilityCheckerMock; } @@ -311,7 +358,7 @@ public function testAddItem($itemId, $item, $count, $withVisibilityChecker, $isV ->willReturnMap($urlReturnValueMap); $this->_block->addItem($itemId, $item); - $this->assertEquals($count, $this->_block->getCount()); + $this->assertEquals($count, $this->_block->getCount(), $itemId); } /** @@ -325,7 +372,8 @@ public function addItemDataProvider() 'item' => ['label' => 'Test 1', 'url' => '*/*/test1'], 'count' => 1, 'withVisibilityChecker' => false, - '$isVisible' => false, + 'isVisible' => false, + 'isAllowed' => true, ], [ 'itemId' => 'test2', @@ -333,21 +381,56 @@ public function addItemDataProvider() 'count' => 1, 'withVisibilityChecker' => false, 'isVisible' => true, + 'isAllowed' => true, ], [ - 'itemId' => 'test1', - 'item' => ['label' => 'Test 1. Hide', 'url' => '*/*/test1'], + 'itemId' => 'test3', + 'item' => ['label' => 'Test 3. Hide', 'url' => '*/*/test3'], 'count' => 0, 'withVisibilityChecker' => true, 'isVisible' => false, + 'isAllowed' => true, ], [ - 'itemId' => 'test2', - 'item' => ['label' => 'Test 2. Does not hide', 'url' => '*/*/test2'], + 'itemId' => 'test4', + 'item' => ['label' => 'Test 4. Does not hide', 'url' => '*/*/test4'], 'count' => 1, 'withVisibilityChecker' => true, 'isVisible' => true, - ] + 'isAllowed' => true, + ], + [ + 'itemId' => 'enable', + 'item' => ['label' => 'Test 5. Not restricted', 'url' => '*/*/test5'], + 'count' => 1, + 'withVisibilityChecker' => true, + 'isVisible' => true, + 'isAllowed' => true, + ], + [ + 'itemId' => 'enable', + 'item' => ['label' => 'Test 5. restricted', 'url' => '*/*/test5'], + 'count' => 0, + 'withVisibilityChecker' => true, + 'isVisible' => true, + 'isAllowed' => false, + ], + [ + 'itemId' => 'refresh', + 'item' => ['label' => 'Test 6. Not Restricted', 'url' => '*/*/test6'], + 'count' => 1, + 'withVisibilityChecker' => true, + 'isVisible' => true, + 'isAllowed' => true, + ], + [ + 'itemId' => 'refresh', + 'item' => ['label' => 'Test 6. Restricted', 'url' => '*/*/test6'], + 'count' => 0, + 'withVisibilityChecker' => true, + 'isVisible' => true, + 'isAllowed' => false, + ], ]; } } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/TabTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/TabTest.php index 1670233324f8e..ad7c6fa99afd2 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/TabTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/TabTest.php @@ -34,6 +34,9 @@ public function testGetters($method, $field, $value, $expected) $this->assertEquals($expected, $object->{$method}()); } + /** + * @return array + */ public function dataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php index b1911da024227..ac0f4a2f467c8 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php @@ -38,7 +38,7 @@ public function testExecute() $messageManagerParams = $helper->getConstructArguments(\Magento\Framework\Message\Manager::class); $messageManagerParams['exceptionMessageFactory'] = $exceptionMessageFactory; $messageManager = $this->getMockBuilder(\Magento\Framework\Message\Manager::class) - ->setMethods(['addSuccess']) + ->setMethods(['addSuccessMessage']) ->setConstructorArgs($messageManagerParams) ->getMock(); @@ -86,7 +86,7 @@ public function testExecute() $mergeService->expects($this->once())->method('cleanMergedJsCss'); $messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The JavaScript/CSS cache has been cleaned.'); $valueMap = [ diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php index 40d9ca1aa8996..fc457cd9681e6 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php @@ -76,7 +76,7 @@ public function testExecute() ->with('clean_static_files_cache_after'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The static files cache has been cleaned.'); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php index 556db311748bd..a8b248c611e07 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Test\Unit\Controller\Adminhtml\Cache; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -155,8 +156,8 @@ public function testExecuteInvalidTypeCache() ->willReturn(['someCache']); $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Specified cache type(s) don\'t exist: someCache') + ->method('addErrorMessage') + ->with('These cache type(s) don\'t exist: someCache') ->willReturnSelf(); $this->assertSame($this->redirectMock, $this->controller->execute()); @@ -175,7 +176,7 @@ public function testExecuteWithException() ->willThrowException($exception); $this->messageManagerMock->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, 'An error occurred while disabling cache.') ->willReturnSelf(); @@ -215,7 +216,7 @@ public function testExecuteSuccess() ->method('persist'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('1 cache type(s) disabled.') ->willReturnSelf(); diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php index ad622ca69757a..6eac44a564f6d 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Test\Unit\Controller\Adminhtml\Cache; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -155,8 +156,8 @@ public function testExecuteInvalidTypeCache() ->willReturn(['someCache']); $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Specified cache type(s) don\'t exist: someCache') + ->method('addErrorMessage') + ->with('These cache type(s) don\'t exist: someCache') ->willReturnSelf(); $this->assertSame($this->redirectMock, $this->controller->execute()); @@ -175,7 +176,7 @@ public function testExecuteWithException() ->willThrowException($exception); $this->messageManagerMock->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, 'An error occurred while enabling cache.') ->willReturnSelf(); @@ -215,7 +216,7 @@ public function testExecuteSuccess() ->method('persist'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('1 cache type(s) enabled.') ->willReturnSelf(); diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php index e8dcc00345fc6..a985681919f0b 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php @@ -107,7 +107,7 @@ public function testExecute() $this->resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirect); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('We updated lifetime statistic.')); $this->objectManager->expects($this->any()) diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php index 844a821df1c20..a8490d6ba2e58 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php @@ -71,7 +71,7 @@ protected function setUp() ->getMock(); $this->_messagesMock = $this->getMockBuilder(\Magento\Framework\Message\Manager::class) ->disableOriginalConstructor() - ->setMethods(['addSuccess']) + ->setMethods(['addSuccessMessage']) ->getMockForAbstractClass(); $this->_authSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) @@ -221,7 +221,7 @@ public function testSaveAction() $this->_requestMock->setParams($requestParams); - $this->_messagesMock->expects($this->once())->method('addSuccess')->with($this->equalTo($testedMessage)); + $this->_messagesMock->expects($this->once())->method('addSuccessMessage')->with($this->equalTo($testedMessage)); $this->_controller->execute(); } diff --git a/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php b/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php index b7a33ab883b69..50c3a8571b48f 100644 --- a/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Backend/Test/Unit/Helper/DataTest.php @@ -60,6 +60,9 @@ public function testPrepareFilterStringValues(array $inputString, array $expecte $this->assertEquals($expected, $actual); } + /** + * @return array + */ public function getPrepareFilterStringValuesDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php index 391deac5a1f4e..f1a4bc355b08e 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php @@ -120,6 +120,9 @@ public function testRefreshAcl($isUserPassedViaParams) $this->assertSame($aclMock, $this->session->getAcl()); } + /** + * @return array + */ public function refreshAclDataProvider() { return [ @@ -234,6 +237,9 @@ public function testIsAllowed($isUserDefined, $isAclDefined, $isAllowed, $expect $this->assertEquals($expectedResult, $this->session->isAllowed('resource')); } + /** + * @return array + */ public function isAllowedDataProvider() { return [ @@ -254,6 +260,9 @@ public function testFirstPageAfterLogin($isFirstPageAfterLogin) $this->assertEquals($isFirstPageAfterLogin, $this->session->isFirstPageAfterLogin()); } + /** + * @return array + */ public function firstPageAfterLoginDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Model/AuthTest.php b/app/code/Magento/Backend/Test/Unit/Model/AuthTest.php index 4b79d504dad91..4af060b157ed4 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/AuthTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/AuthTest.php @@ -54,7 +54,6 @@ protected function setUp() /** * @expectedException \Magento\Framework\Exception\AuthenticationException - * @expectedExceptionMessage You did not sign in correctly or your account is temporarily disabled. */ public function testLoginFailed() { @@ -64,7 +63,10 @@ public function testLoginFailed() ->with(\Magento\Backend\Model\Auth\Credential\StorageInterface::class) ->will($this->returnValue($this->_credentialStorage)); $exceptionMock = new \Magento\Framework\Exception\LocalizedException( - __('You did not sign in correctly or your account is temporarily disabled.') + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) ); $this->_credentialStorage ->expects($this->once()) @@ -74,5 +76,10 @@ public function testLoginFailed() $this->_credentialStorage->expects($this->never())->method('getId'); $this->_eventManagerMock->expects($this->once())->method('dispatch')->with('backend_auth_user_login_failed'); $this->_model->login('username', 'password'); + + $this->expectExceptionMessage( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ); } } diff --git a/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php b/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php index 31a13191750a3..cce83c33a2aaa 100755 --- a/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Config/SessionLifetime/BackendModelTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Test\Unit\Model\Config\SessionLifetime; use Magento\Backend\Model\Config\SessionLifetime\BackendModel; @@ -20,23 +21,28 @@ public function testBeforeSave($value, $errorMessage = null) \Magento\Backend\Model\Config\SessionLifetime\BackendModel::class ); if ($errorMessage !== null) { - $this->expectException(\Magento\Framework\Exception\LocalizedException::class, $errorMessage); + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage($errorMessage); } $model->setValue($value); $object = $model->beforeSave(); $this->assertEquals($model, $object); } + /** + * @return array + */ public function adminSessionLifetimeDataProvider() { return [ [ BackendModel::MIN_LIFETIME - 1, - 'Admin session lifetime must be greater than or equal to 60 seconds' + 'The Admin session lifetime is invalid. Set the lifetime to 60 seconds or longer and try again.' ], [ BackendModel::MAX_LIFETIME + 1, - 'Admin session lifetime must be less than or equal to 31536000 seconds (one year)' + 'The Admin session lifetime is invalid. ' + . 'Set the lifetime to 31536000 seconds (one year) or shorter and try again.' ], [ 900 diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php index bc18bd44f4be4..260a38a481b3c 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/ConfigTest.php @@ -96,7 +96,7 @@ public function testGetMenuWithCachedObjectReturnsUnserializedObject() $this->assertEquals($this->menuMock, $this->model->getMenu()); } - public function testGetMenuWithNotCachedObjectBuidlsObject() + public function testGetMenuWithNotCachedObjectBuildsObject() { $this->cacheInstanceMock->expects( $this->at(0) @@ -140,6 +140,9 @@ public function testGetMenuExceptionLogged($expectedException) $this->model->getMenu(); } + /** + * @return array + */ public function getMenuExceptionLoggedDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php index 3c1f1e43900be..dec85f4b98e3d 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Item/ValidatorTest.php @@ -79,6 +79,9 @@ public function testValidateWithMissingRequiredParamThrowsException($requiredPar } } + /** + * @return array + */ public function requiredParamsProvider() { return [['id'], ['title'], ['resource']]; @@ -102,6 +105,9 @@ public function testValidateWithNonValidPrimitivesThrowsException($param, $inval } } + /** + * @return array + */ public function invalidParamsProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php b/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php index 23d1ed5da1425..5d026a2b1fc32 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/MenuBuilderTest.php @@ -35,6 +35,9 @@ public function testAfterGetResult($isPub, $times) ); } + /** + * @return array + */ public function afterGetResultDataProvider() { return [[true, 1], [false, 0],]; diff --git a/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php b/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php index 49fcdf4fc8770..00ae8c2f44a69 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Session/AdminConfigTest.php @@ -136,6 +136,9 @@ public function testSetSessionSettingsByConstructor($secureRequest) $this->assertSame($secureRequest, $adminConfig->getCookieSecure()); } + /** + * @return array + */ public function requestSecureDataProvider() { return [[true], [false]]; diff --git a/app/code/Magento/Backend/Test/Unit/Model/Widget/Grid/ParserTest.php b/app/code/Magento/Backend/Test/Unit/Model/Widget/Grid/ParserTest.php index 569c5ffc16c9c..98f1965477b2c 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Widget/Grid/ParserTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Widget/Grid/ParserTest.php @@ -58,6 +58,9 @@ public function testIsOperation($operation, $expected) $this->assertEquals($expected, $this->_model->isOperation($operation)); } + /** + * @return array + */ public function isOperationDataProvider() { return [ diff --git a/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php b/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php new file mode 100644 index 0000000000000..c7ff1d95617b6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Test\Unit\Service\V1; + +use Magento\Backend\Service\V1\ModuleService; +use Magento\Framework\Module\ModuleListInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * Module List Service Test + * + * Covers \Magento\Sales\Model\ValidatorResultMerger + */ +class ModuleServiceTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var ModuleService + */ + private $moduleService; + + /** + * @var ModuleListInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $moduleListMock; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->moduleListMock = $this->createMock(ModuleListInterface::class); + $this->objectManager = new ObjectManager($this); + $this->moduleService = $this->objectManager->getObject( + ModuleService::class, + [ + 'moduleList' => $this->moduleListMock, + ] + ); + } + + /** + * Test getModules method + * + * @return void + */ + public function testGetModules() + { + $moduleNames = ['Magento_Backend', 'Magento_Catalog', 'Magento_Customer']; + $this->moduleListMock->expects($this->once())->method('getNames')->willReturn($moduleNames); + + $expected = $moduleNames; + $actual = $this->moduleService->getModules(); + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Backend/Ui/Component/Control/DeleteButton.php b/app/code/Magento/Backend/Ui/Component/Control/DeleteButton.php new file mode 100644 index 0000000000000..3f4f3669ab75b --- /dev/null +++ b/app/code/Magento/Backend/Ui/Component/Control/DeleteButton.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Ui\Component\Control; + +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Escaper; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Represents delete button with pre-configured options + * Provide an ability to show confirmation message on click on the "Delete" button + * + * @api + */ +class DeleteButton implements ButtonProviderInterface +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @var string + */ + private $confirmationMessage; + + /** + * @var string + */ + private $idFieldName; + + /** + * @var string + */ + private $deleteRoutePath; + + /** + * @var int + */ + private $sortOrder; + + /** + * @param RequestInterface $request + * @param UrlInterface $urlBuilder + * @param Escaper $escaper + * @param string $confirmationMessage + * @param string $idFieldName + * @param string $deleteRoutePath + * @param int $sortOrder + */ + public function __construct( + RequestInterface $request, + UrlInterface $urlBuilder, + Escaper $escaper, + string $confirmationMessage, + string $idFieldName, + string $deleteRoutePath, + int $sortOrder + ) { + $this->request = $request; + $this->urlBuilder = $urlBuilder; + $this->escaper = $escaper; + $this->confirmationMessage = $confirmationMessage; + $this->idFieldName = $idFieldName; + $this->deleteRoutePath = $deleteRoutePath; + $this->sortOrder = $sortOrder; + } + + /** + * {@inheritdoc} + */ + public function getButtonData() + { + $data = []; + $fieldId = $this->escaper->escapeJs($this->escaper->escapeHtml($this->request->getParam($this->idFieldName))); + if (null !== $fieldId) { + $url = $this->urlBuilder->getUrl($this->deleteRoutePath); + $escapedMessage = $this->escaper->escapeJs($this->escaper->escapeHtml($this->confirmationMessage)); + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'on_click' => "deleteConfirm('{$escapedMessage}', '{$url}', {data:{{$this->idFieldName}:{$fieldId}}})", + 'sort_order' => $this->sortOrder, + ]; + } + return $data; + } +} diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index fbbecb25efa50..f9408768136bb 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -5,30 +5,29 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/framework": "100.3.*", - "magento/module-backup": "100.3.*", - "magento/module-catalog": "101.2.*", - "magento/module-config": "100.3.*", - "magento/module-customer": "100.3.*", - "magento/module-developer": "100.3.*", - "magento/module-directory": "100.3.*", - "magento/module-eav": "100.3.*", - "magento/module-quote": "100.3.*", - "magento/module-reports": "100.3.*", - "magento/module-require-js": "100.3.*", - "magento/module-sales": "100.3.*", - "magento/module-security": "100.3.*", - "magento/module-store": "100.3.*", - "magento/module-translation": "100.3.*", - "magento/module-ui": "100.3.*", - "magento/module-user": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-backup": "*", + "magento/module-catalog": "*", + "magento/module-config": "*", + "magento/module-customer": "*", + "magento/module-developer": "*", + "magento/module-directory": "*", + "magento/module-eav": "*", + "magento/module-quote": "*", + "magento/module-reports": "*", + "magento/module-require-js": "*", + "magento/module-sales": "*", + "magento/module-security": "*", + "magento/module-store": "*", + "magento/module-translation": "*", + "magento/module-ui": "*", + "magento/module-user": "*" }, "suggest": { - "magento/module-theme": "100.3.*" + "magento/module-theme": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Backend/etc/acl.xml b/app/code/Magento/Backend/etc/acl.xml index af4ab5856e94c..cf9471e75bed9 100644 --- a/app/code/Magento/Backend/etc/acl.xml +++ b/app/code/Magento/Backend/etc/acl.xml @@ -38,7 +38,21 @@ <resource id="Magento_Backend::custom" title="Package Extensions" translate="title" sortOrder="20" /> </resource> <resource id="Magento_Backend::tools" title="Tools" translate="title" sortOrder="50"> - <resource id="Magento_Backend::cache" title="Cache Management" translate="title" sortOrder="10" /> + <resource id="Magento_Backend::cache" title="Cache Management" translate="title" sortOrder="10"> + <resource id="Magento_Backend::main_actions" title="Clean Cache Actions" translate="title" sortOrder="10"> + <resource id="Magento_Backend::flush_cache_storage" title="Flush Cache Storage" translate="title" sortOrder="10" /> + <resource id="Magento_Backend::flush_magento_cache" title="Flush Magento Cache" translate="title" sortOrder="20" /> + </resource> + <resource id="Magento_Backend::mass_actions" title="Cache Types Management" translate="title" sortOrder="20"> + <resource id="Magento_Backend::toggling_cache_type" title="Toggle Cache Type" translate="title" sortOrder="10" /> + <resource id="Magento_Backend::refresh_cache_type" title="Refresh Cache Type" translate="title" sortOrder="20" /> + </resource> + <resource id="Magento_Backend::additional_cache_management" title="Additional Cache Management" translate="title" sortOrder="30"> + <resource id="Magento_Backend::flush_catalog_images" title="Catalog Images Cache" translate="title" sortOrder="10" /> + <resource id="Magento_Backend::flush_js_css" title="Flush Js/Css" translate="title" sortOrder="20" /> + <resource id="Magento_Backend::flush_static_files" title="Flush Static Files" translate="title" sortOrder="30" /> + </resource> + </resource> <resource id="Magento_Backend::setup_wizard" title="Web Setup Wizard" translate="title" sortOrder="20" /> </resource> <resource id="Magento_Backend::system_other_settings" title="Other Settings" translate="title" sortOrder="80" /> diff --git a/app/code/Magento/Backend/etc/adminhtml/di.xml b/app/code/Magento/Backend/etc/adminhtml/di.xml index 050115087c26e..3384384343fe9 100644 --- a/app/code/Magento/Backend/etc/adminhtml/di.xml +++ b/app/code/Magento/Backend/etc/adminhtml/di.xml @@ -139,10 +139,16 @@ <type name="Magento\Backend\Model\Menu\Builder"> <plugin name="SetupMenuBuilder" type="Magento\Backend\Model\Setup\MenuBuilder" /> </type> - <type name="Magento\Config\Model\Config\Structure\ConcealInProductionConfigList"> + <type name="Magento\Config\Model\Config\Structure\ElementVisibility\ConcealInProduction"> <arguments> <argument name="configs" xsi:type="array"> <item name="dev" xsi:type="const">Magento\Config\Model\Config\Structure\ElementVisibilityInterface::HIDDEN</item> + </argument> + </arguments> + </type> + <type name="Magento\Config\Model\Config\Structure\ElementVisibility\ConcealInProductionWithoutScdOnDemand"> + <arguments> + <argument name="configs" xsi:type="array"> <item name="general/locale/code" xsi:type="const">Magento\Config\Model\Config\Structure\ElementVisibilityInterface::DISABLED</item> </argument> </arguments> @@ -156,4 +162,10 @@ </argument> </arguments> </type> + <type name="Magento\Framework\View\Layout\Generator\Block"> + <arguments> + <argument name="defaultClass" xsi:type="string">Magento\Backend\Block\Template</argument> + </arguments> + </type> + <preference for="CsrfRequestValidator" type="Magento\Backend\App\Request\BackendValidator" /> </config> diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 92df39bd65ee4..e061455acbe6b 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -136,7 +136,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="template_hints_blocks" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Add Block Names to Hints</label> + <label>Add Block Class Type to Hints</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> @@ -197,7 +197,7 @@ </group> <group id="image" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Image Processing Settings</label> - <field id="default_adapter" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="default_adapter" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Image Adapter</label> <source_model>Magento\Config\Model\Config\Source\Image\Adapter</source_model> <backend_model>Magento\Config\Model\Config\Backend\Image\Adapter</backend_model> @@ -231,9 +231,10 @@ <label>European Union Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> - <field id="destinations" translate="label" type="multiselect" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="destinations" translate="label" type="multiselect" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Top destinations</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> + <can_be_empty>1</can_be_empty> </field> </group> <group id="locale" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -314,11 +315,11 @@ <label>Disable Email Communications</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="host" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="host" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Host</label> <comment>For Windows server only.</comment> </field> - <field id="port" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="port" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Port (25)</label> <comment>For Windows server only.</comment> </field> @@ -335,6 +336,19 @@ </depends> </field> </group> + <group id="upload_configuration" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Images Upload Configuration</label> + <field id="max_width" translate="label comment" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Maximum Width</label> + <validate>validate-greater-than-zero validate-number required-entry</validate> + <comment>Maximum allowed width for uploaded image.</comment> + </field> + <field id="max_height" translate="label comment" type="text" sortOrder="200" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Maximum Height</label> + <validate>validate-greater-than-zero validate-number required-entry</validate> + <comment>Maximum allowed height for uploaded image.</comment> + </field> + </group> </section> <section id="admin" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Admin</label> @@ -425,17 +439,17 @@ <label>Web</label> <tab>general</tab> <resource>Magento_Config::web</resource> - <group id="url" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="url" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Url Options</label> <field id="use_store" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Add Store Code to Urls</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Store</backend_model> <comment> - <![CDATA[<strong style="color:red">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third party services (e.g. PayPal etc.).]]> + <![CDATA[<strong style="color:red">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).]]> </comment> </field> - <field id="redirect_to_base" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="redirect_to_base" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Auto-redirect to Base URL</label> <source_model>Magento\Config\Model\Config\Source\Web\Redirect</source_model> <comment>I.e. redirect from http://example.com/store/ to http://www.example.com/store/</comment> @@ -448,7 +462,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> - <group id="unsecure" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="unsecure" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Base URLs</label> <comment>Any of the fields allow fully qualified URLs that end with '/' (slash) e.g. http://example.com/magento/</comment> <field id="base_url" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -456,7 +470,7 @@ <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>Specify URL or {{base_url}} placeholder.</comment> </field> - <field id="base_link_url" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="base_link_url" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Base Link URL</label> <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May start with {{unsecure_base_url}} placeholder.</comment> @@ -466,13 +480,13 @@ <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May be empty or start with {{unsecure_base_url}} placeholder.</comment> </field> - <field id="base_media_url" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="base_media_url" translate="label comment" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Base URL for User Media Files</label> <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May be empty or start with {{unsecure_base_url}} placeholder.</comment> </field> </group> - <group id="secure" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="secure" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Base URLs (Secure)</label> <comment>Any of the fields allow fully qualified URLs that end with '/' (slash) e.g. https://example.com/magento/</comment> <field id="base_url" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -480,7 +494,7 @@ <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>Specify URL or {{base_url}}, or {{unsecure_base_url}} placeholder.</comment> </field> - <field id="base_link_url" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="base_link_url" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Secure Base Link URL</label> <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May start with {{secure_base_url}} or {{unsecure_base_url}} placeholder.</comment> @@ -490,24 +504,24 @@ <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May be empty or start with {{secure_base_url}}, or {{unsecure_base_url}} placeholder.</comment> </field> - <field id="base_media_url" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="base_media_url" translate="label comment" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Secure Base URL for User Media Files</label> <backend_model>Magento\Config\Model\Config\Backend\Baseurl</backend_model> <comment>May be empty or start with {{secure_base_url}}, or {{unsecure_base_url}} placeholder.</comment> </field> - <field id="use_in_frontend" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="use_in_frontend" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Use Secure URLs on Storefront</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> <comment>Enter https protocol to use Secure URLs on Storefront.</comment> </field> - <field id="use_in_adminhtml" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_in_adminhtml" translate="label comment" type="select" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Use Secure URLs in Admin</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> <comment>Enter https protocol to use Secure URLs in Admin.</comment> </field> - <field id="enable_hsts" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="enable_hsts" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Enable HTTP Strict Transport Security (HSTS)</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> @@ -517,7 +531,7 @@ <field id="use_in_adminhtml">1</field> </depends> </field> - <field id="enable_upgrade_insecure" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="enable_upgrade_insecure" translate="label comment" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Upgrade Insecure Requests</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml index b7aaf8bf20dba..45d283ad3ff22 100644 --- a/app/code/Magento/Backend/etc/config.xml +++ b/app/code/Magento/Backend/etc/config.xml @@ -28,6 +28,10 @@ <dashboard> <enable_charts>1</enable_charts> </dashboard> + <upload_configuration> + <max_width>1920</max_width> + <max_height>1200</max_height> + </upload_configuration> </system> <general> <validator_data> diff --git a/app/code/Magento/Backend/etc/module.xml b/app/code/Magento/Backend/etc/module.xml index 57e00489391f2..3a5cd8226753d 100644 --- a/app/code/Magento/Backend/etc/module.xml +++ b/app/code/Magento/Backend/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Backend" setup_version="2.0.0"> + <module name="Magento_Backend"> <sequence> <module name="Magento_Directory"/> </sequence> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index f9f44f547e25b..bfedd56b14313 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -405,9 +405,9 @@ Web,Web "Url Options","Url Options" "Add Store Code to Urls","Add Store Code to Urls" " - <strong style=""color:red"">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third party services (e.g. PayPal etc.). + <strong style=""color:red"">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.). "," - <strong style=""color:red"">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third party services (e.g. PayPal etc.). + <strong style=""color:red"">Warning!</strong> When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.). " "Auto-redirect to Base URL","Auto-redirect to Base URL" "Search Engine Optimization","Search Engine Optimization" @@ -447,7 +447,7 @@ Tags,Tags "<h1 class=""page-heading"">404 Error</h1><p>Page not found.</p>","<h1 class=""page-heading"">404 Error</h1><p>Page not found.</p>" "Community Edition","Community Edition" "Default Theme","Default Theme" -"If no value is specified, the system default is used. The system default may be modified by third party extensions.","If no value is specified, the system default is used. The system default may be modified by third party extensions." +"If no value is specified, the system default is used. The system default may be modified by third-party extensions.","If no value is specified, the system default is used. The system default may be modified by third-party extensions." "Applied Theme","Applied Theme" "Design Rule","Design Rule" "User Agent Rules","User Agent Rules" diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml index ab5ddc414b51f..4bbe70b6cdb92 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_index.xml @@ -11,7 +11,11 @@ <body> <referenceContainer name="content"> <block class="Magento\Backend\Block\Cache" name="adminhtml.cache.container"/> - <block class="Magento\Backend\Block\Cache\Additional" name="cache.additional" template="Magento_Backend::system/cache/additional.phtml"/> + <block class="Magento\Backend\Block\Cache\Additional" name="cache.additional" template="Magento_Backend::system/cache/additional.phtml"> + <arguments> + <argument name="permissions" xsi:type="object">Magento\Backend\Block\Cache\Permissions</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml index 805e9783f3f18..52d5dd6d114ee 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml @@ -43,7 +43,7 @@ data-validate="{required:true}" value="" placeholder="<?= /* @escapeNotVerified */ __('password') ?>" - autocomplete="new-password" + autocomplete="off" /> </div> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml index 1e14dd837634a..966372773f295 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml @@ -13,8 +13,8 @@ data-mage-init='{ "Magento_Backend/js/media-uploader" : { "maxFileSize": <?= /* @escapeNotVerified */ $block->getFileSizeService()->getMaxFileSize() ?>, - "maxWidth":<?= /* @escapeNotVerified */ \Magento\Framework\File\Uploader::MAX_IMAGE_WIDTH ?> , - "maxHeight": <?= /* @escapeNotVerified */ \Magento\Framework\File\Uploader::MAX_IMAGE_HEIGHT ?> + "maxWidth":<?= /* @escapeNotVerified */ $block->getImageUploadMaxWidth() ?> , + "maxHeight": <?= /* @escapeNotVerified */ $block->getImageUploadMaxHeight() ?> } }' > diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml index 40b7173f47417..f952001f5e2ff 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml @@ -17,7 +17,7 @@ <?= /* @escapeNotVerified */ $edition ?> class="logo"> <img class="logo-img" src="<?= /* @escapeNotVerified */ $block->getViewFileUrl($logoSrc) ?>" - alt="<?= $block->escapeHtml(__('Magento Admin Panel')) ?>"/> + alt="<?= $block->escapeHtml(__('Magento Admin Panel')) ?>" title="<?= $block->escapeHtml(__('Magento Admin Panel')) ?>"/> </a> <?php break; ?> <?php case 'user': ?> @@ -29,7 +29,7 @@ data-mage-init='{"dropdown":{}}' data-toggle="dropdown"> <span class="admin__action-dropdown-text"> - <span class="admin-user-account-text"><?= $block->escapeHtml($block->getUser()->getUsername()) ?></span> + <span class="admin-user-account-text"><?= $block->escapeHtml($block->getUser()->getUserName()) ?></span> </span> </a> <ul class="admin__action-dropdown-menu"> @@ -39,7 +39,7 @@ href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/system_account/index') ?>" <?= /* @escapeNotVerified */ $block->getUiId('user', 'account', 'settings') ?> title="<?= $block->escapeHtml(__('Account Setting')) ?>"> - <?= /* @escapeNotVerified */ __('Account Setting') ?> (<span class="admin-user-name"><?= $block->escapeHtml($block->getUser()->getUsername()) ?></span>) + <?= /* @escapeNotVerified */ __('Account Setting') ?> (<span class="admin-user-name"><?= $block->escapeHtml($block->getUser()->getUserName()) ?></span>) </a> </li> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml index 3a3dbd99bfba9..4ef6d378cc4a4 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml @@ -8,5 +8,7 @@ ?> <?php if ($block->getBugreportUrl()): ?> - <a class="link-report" href="<?= /* @escapeNotVerified */ $block->getBugreportUrl() ?>" id="footer_bug_tracking"><?= /* @escapeNotVerified */ __('Report an Issue') ?></a> + <a class="link-report" href="<?= /* @escapeNotVerified */ $block->getBugreportUrl() ?>" id="footer_bug_tracking" target="_blank"> + <?= /* @escapeNotVerified */ __('Report an Issue') ?> + </a> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml index 84bbc4ea601ef..8e30afdf51f7f 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml @@ -5,34 +5,39 @@ */ // @codingStandardsIgnoreFile + +/** @var \Magento\Backend\Block\Cache\Permissions|null $permissions */ +$permissions = $block->getData('permissions'); ?> -<div class="additional-cache-management"> - <h2> - <span><?= /* @escapeNotVerified */ __('Additional Cache Management') ?></span> - </h2> - <p> - <button onclick="setLocation('<?= /* @escapeNotVerified */ $block->getCleanImagesUrl() ?>')" type="button"> - <?= /* @escapeNotVerified */ __('Flush Catalog Images Cache') ?> - </button> - <span><?= /* @escapeNotVerified */ __('Pregenerated product images files') ?></span> - </p> - <p> - <button onclick="setLocation('<?= /* @escapeNotVerified */ $block->getCleanMediaUrl() ?>')" type="button"> - <?= /* @escapeNotVerified */ __('Flush JavaScript/CSS Cache') ?> - </button> - <span><?= /* @escapeNotVerified */ __('Themes JavaScript and CSS files combined to one file') ?></span> - </p> - <?php - if (!$block->isInProductionMode()): - ?> - <p> - <button onclick="setLocation('<?= /* @escapeNotVerified */ $block->getCleanStaticFilesUrl() ?>')" type="button"> - <?= /* @escapeNotVerified */ __('Flush Static Files Cache') ?> - </button> - <span><?= /* @escapeNotVerified */ __('Preprocessed view files and static files') ?></span> - </p> - <?php - endif; - ?> - <?= $block->getChildHtml() ?> -</div> +<?php if ($permissions && $permissions->hasAccessToAdditionalActions()): ?> + <div class="additional-cache-management"> + <?php if ($permissions->hasAccessToFlushCatalogImages()): ?> + <h2> + <span><?= $block->escapeHtml(__('Additional Cache Management')); ?></span> + </h2> + <p> + <button onclick="setLocation('<?= $block->escapeJs($block->getCleanImagesUrl()); ?>')" type="button"> + <?= $block->escapeHtml(__('Flush Catalog Images Cache')); ?> + </button> + <span><?= $block->escapeHtml(__('Pregenerated product images files')); ?></span> + </p> + <?php endif; ?> + <?php if ($permissions->hasAccessToFlushJsCss()): ?> + <p> + <button onclick="setLocation('<?= $block->escapeJs($block->getCleanMediaUrl()); ?>')" type="button"> + <?= $block->escapeHtml(__('Flush JavaScript/CSS Cache')); ?> + </button> + <span><?= $block->escapeHtml(__('Themes JavaScript and CSS files combined to one file')) ?></span> + </p> + <?php endif; ?> + <?php if (!$block->isInProductionMode() && $permissions->hasAccessToFlushStaticFiles()): ?> + <p> + <button onclick="setLocation('<?= $block->escapeJs($block->getCleanStaticFilesUrl()); ?>')" type="button"> + <?= $block->escapeHtml(__('Flush Static Files Cache')); ?> + </button> + <span><?= $block->escapeHtml(__('Preprocessed view files and static files')); ?></span> + </p> + <?php endif; ?> + <?= $block->getChildHtml() ?> + </div> +<?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml index a528133b2bc3a..af369800287c1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml @@ -28,21 +28,21 @@ <script data-template="search-suggest" type="text/x-magento-template"> <ul class="search-global-menu"> <li class="item"> - <a id="searchPreviewProducts" href="<?= /* @escapeNotVerified */ $block->getURL('catalog/product/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Products</a> + <a id="searchPreviewProducts" href="<?= /* @escapeNotVerified */ $block->getUrl('catalog/product/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Products</a> </li> <li class="item"> - <a id="searchPreviewOrders" href="<?= /* @escapeNotVerified */ $block->getURL('sales/order/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Orders</a> + <a id="searchPreviewOrders" href="<?= /* @escapeNotVerified */ $block->getUrl('sales/order/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Orders</a> </li> <li class="item"> - <a id="searchPreviewCustomers" href="<?= /* @escapeNotVerified */ $block->getURL('customer/index/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Customers</a> + <a id="searchPreviewCustomers" href="<?= /* @escapeNotVerified */ $block->getUrl('customer/index/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Customers</a> </li> <li class="item"> - <a id="searchPreviewPages" href="<?= /* @escapeNotVerified */ $block->getURL('cms/page/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Pages</a> + <a id="searchPreviewPages" href="<?= /* @escapeNotVerified */ $block->getUrl('cms/page/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Pages</a> </li> <% if (data.items.length) { %> <% _.each(data.items, function(value){ %> <li class="item" - <%- data.optionData(value) %> + <%= data.optionData(value) %> > <a href="<%- value.url %>" class="title"><%- value.name %></a> <span class="type"><%- value.type %></span> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml index 27127e54e5be2..a115777624e91 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml @@ -41,11 +41,11 @@ <?php endif; ?> </div> -<script> -require(['jquery'], function($){ - $('.actions-split') - .on('click.splitDefault', '.action-default', function() { - $(this).siblings('.dropdown-menu').find('.item-default').trigger('click'); - }); -}); +<script type="text/x-magento-init"> + { + ".actions-split": { + "Magento_Ui/js/grid/controls/button/split": {} + } + } </script> + diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml index 81f38a30ebd17..720bc1e58259d 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml @@ -46,9 +46,6 @@ <input type="<?= /* @escapeNotVerified */ $element->getType() ?>" name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $element->getValue() ?>" class="input-text <?= /* @escapeNotVerified */ $element->getClass() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>"/> </span> <?php break; - case 'hidden': ?> - <input type="<?= /* @escapeNotVerified */ $element->getType() ?>" name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $element->getValue() ?>"> - <?php break; case 'radios': ?> <span class="form_row"> <label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml index 4f8d9a56a38a3..e11c0efc123ff 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml @@ -34,7 +34,7 @@ <?php foreach ($block->getValues()->getAttributeBackend()->getImageTypes() as $type): ?> <td class="gallery" align="center" style="vertical-align:bottom;"> <a href="<?= /* @escapeNotVerified */ $image->setType($type)->getSourceUrl() ?>" target="_blank" onclick="imagePreview('<?= $block->getElement()->getHtmlId() ?>_image_<?= /* @escapeNotVerified */ $type ?>_<?= /* @escapeNotVerified */ $image->getValueId() ?>');return false;"> - <img id="<?= $block->getElement()->getHtmlId() ?>_image_<?= /* @escapeNotVerified */ $type ?>_<?= /* @escapeNotVerified */ $image->getValueId() ?>" src="<?= /* @escapeNotVerified */ $image->setType($type)->getSourceUrl() ?>?<?= /* @escapeNotVerified */ time() ?>" alt="<?= /* @escapeNotVerified */ $image->getValue() ?>" height="25" class="small-image-preview v-middle"/></a><br/> + <img id="<?= $block->getElement()->getHtmlId() ?>_image_<?= /* @escapeNotVerified */ $type ?>_<?= /* @escapeNotVerified */ $image->getValueId() ?>" src="<?= /* @escapeNotVerified */ $image->setType($type)->getSourceUrl() ?>?<?= /* @escapeNotVerified */ time() ?>" alt="<?= /* @escapeNotVerified */ $image->getValue() ?>" title="<?= /* @escapeNotVerified */ $image->getValue() ?>" height="25" class="small-image-preview v-middle"/></a><br/> <input type="file" name="<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>_<?= /* @escapeNotVerified */ $type ?>[<?= /* @escapeNotVerified */ $image->getValueId() ?>]" size="1"></td> <?php endforeach; ?> <td class="gallery" align="center" style="vertical-align:bottom;"><input type="input" name="<?= /* @escapeNotVerified */ $block->getElement()->getParentName() ?>[position][<?= /* @escapeNotVerified */ $image->getValueId() ?>]" value="<?= /* @escapeNotVerified */ $image->getPosition() ?>" id="<?= $block->getElement()->getHtmlId() ?>_position_<?= /* @escapeNotVerified */ $image->getValueId() ?>" size="3"/></td> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index c99759a60d1bf..fad8f5968009f 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -19,7 +19,7 @@ * */ /* @var $block \Magento\Backend\Block\Widget\Grid */ -$numColumns = sizeof($block->getColumns()); +$numColumns = !is_null($block->getColumns()) ? sizeof($block->getColumns()) : 0; ?> <?php if ($block->getCollection()): ?> @@ -107,7 +107,7 @@ $numColumns = sizeof($block->getColumns()); <?= /* @escapeNotVerified */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> <?php if ($_curPage < $_lastPage): ?> - <button title="<?= /* @escapeNotVerified */ __('Next page') ?>" + <button type="button" title="<?= /* @escapeNotVerified */ __('Next page') ?>" class="action-next" onclick="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setPage('<?= /* @escapeNotVerified */ ($_curPage + 1) ?>');return false;"> <span><?= /* @escapeNotVerified */ __('Next page') ?></span> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml index a31bf4d23abaa..f97db4ad993b1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml @@ -72,7 +72,7 @@ $numColumns = sizeof($block->getColumns()); <?php if ($block->getPagerVisibility()): ?> <div class="admin__data-grid-pager-wrap"> <select name="<?= /* @escapeNotVerified */ $block->getVarNameLimit() ?>" - id="<?= $block->escapeHTML($block->getHtmlId()) ?>_page-limit" + id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" onchange="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.loadByElement(this)" class="admin__control-select"> <option value="20"<?php if ($block->getCollection()->getPageSize() == 20): ?> @@ -91,7 +91,7 @@ $numColumns = sizeof($block->getColumns()); selected="selected"<?php endif; ?>>200 </option> </select> - <label for="<?= $block->escapeHTML($block->getHtmlId()) ?><?= $block->escapeHTML($block->getHtmlId()) ?>_page-limit" + <label for="<?= $block->escapeHtml($block->getHtmlId()) ?><?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" class="admin__control-support-text"><?= /* @escapeNotVerified */ __('per page') ?></label> <div class="admin__data-grid-pager"> @@ -107,12 +107,12 @@ $numColumns = sizeof($block->getColumns()); <button type="button" class="action-previous disabled"><span><?= /* @escapeNotVerified */ __('Previous page') ?></span></button> <?php endif; ?> <input type="text" - id="<?= $block->escapeHTML($block->getHtmlId()) ?>_page-current" + id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current" name="<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>" value="<?= /* @escapeNotVerified */ $_curPage ?>" class="admin__control-text" onkeypress="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.inputPage(event, '<?= /* @escapeNotVerified */ $_lastPage ?>')" <?= /* @escapeNotVerified */ $block->getUiId('current-page') ?> /> - <label class="admin__control-support-text" for="<?= $block->escapeHTML($block->getHtmlId()) ?>_page-current"> + <label class="admin__control-support-text" for="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current"> <?= /* @escapeNotVerified */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> <?php if ($_curPage < $_lastPage): ?> diff --git a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml index 19a4ab1388006..79c987383299f 100644 --- a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml @@ -12,7 +12,7 @@ </settings> <field name="theme_theme_id" sortOrder="10" formElement="select"> <settings> - <notice translate="true">If no value is specified, the system default is used. The system default may be modified by third party extensions.</notice> + <notice translate="true">If no value is specified, the system default is used. The system default may be modified by third-party extensions.</notice> <dataType>text</dataType> <label translate="true">Applied Theme</label> <dataScope>theme_theme_id</dataScope> @@ -92,7 +92,7 @@ </select> </formElements> </field> - <actionDelete template="Magento_Backend/dynamic-rows/cells/action-delete" sortOrder="50"> + <actionDelete template="Magento_Backend/dynamic-rows/cells/action-delete"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="fit" xsi:type="boolean">false</item> diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js index 03403a4ec4a04..7e0b6bdfb46dd 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js @@ -13,11 +13,19 @@ define([ 'jquery', 'mage/template', 'Magento_Ui/js/modal/alert', + 'Magento_Ui/js/form/element/file-uploader', 'mage/translate', 'jquery/file-uploader' -], function ($, mageTemplate, alert) { +], function ($, mageTemplate, alert, FileUploader) { 'use strict'; + var fileUploader = new FileUploader({ + dataScope: '', + isMultipleFiles: true + }); + + fileUploader.initUploader(); + $.widget('mage.mediaUploader', { /** @@ -79,10 +87,9 @@ define([ if (data.result && !data.result.error) { self.element.trigger('addItem', data.result); } else { - alert({ - content: $.mage.__('We don\'t recognize or support this file extension type.') - }); + fileUploader.aggregateError(data.files[0].name, data.result.error); } + self.element.find('#' + data.fileId).remove(); }, @@ -108,17 +115,18 @@ define([ .delay(2000) .hide('highlight') .remove(); - } + }, + + stop: fileUploader.uploaderConfig.stop }); this.element.find('input[type=file]').fileupload('option', { process: [{ action: 'load', - fileTypes: /^image\/(gif|jpeg|png)$/ + fileTypes: /^image\/(gif|jpeg|png)$/, + maxFileSize: this.options.maxFileSize }, { - action: 'resize', - maxWidth: this.options.maxWidth, - maxHeight: this.options.maxHeight + action: 'resize' }, { action: 'save' }] diff --git a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html index 74621806fa5fb..fe30ca7e83f19 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html +++ b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html @@ -5,7 +5,7 @@ */ --> -<div class="admin__field-complex" if="element.addButton"> +<div class="admin__field-complex" if="$data.addButton"> <div class="admin__field-complex-title"> <span class="label" translate="'User Agent Rules'"></span> </div> @@ -27,10 +27,10 @@ <div class="admin__field admin__field-wide" visible="visible" disabled="disabled" - css="element.setClasses(element)" + css="$data.setClasses($data)" attr="'data-index': index"> - <label if="element.label" class="admin__field-label" attr="for: element.uid"> - <span translate="element.label"/> + <label if="$data.label" class="admin__field-label" attr="for: $data.uid"> + <span translate="$data.label"/> </label> <div class="admin__field-control" data-role="grid-wrapper"> @@ -85,7 +85,7 @@ <div class="messages"> <div class="message message-notice notice"> <span - translate="'Search strings are either normal strings or regular exceptions (PCRE). They are matched in the same order as entered.'"></span> + translate="'Search strings are either normal strings or regular expressions (PCRE). They are matched in the same order as entered.'"></span> <br> <span translate="'Examples'"></span>: diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php index ee0370c8ecb29..53f45aff50cbc 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php @@ -12,14 +12,14 @@ class Create extends \Magento\Backup\Controller\Adminhtml\Index { /** - * Create backup action + * Create backup action. * * @return void|\Magento\Backend\App\Action * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function execute() { - if (!$this->getRequest()->isAjax()) { + if (!$this->isRequestAllowed()) { return $this->_redirect('*/*/index'); } @@ -82,7 +82,7 @@ public function execute() $backupManager->create(); - $this->messageManager->addSuccess($successMessage); + $this->messageManager->addSuccessMessage($successMessage); $response->setRedirectUrl($this->getUrl('*/*/index')); } catch (\Magento\Framework\Backup\Exception\NotEnoughFreeSpace $e) { @@ -106,4 +106,14 @@ public function execute() $this->getResponse()->representJson($response->toJson()); } + + /** + * Check if request is allowed. + * + * @return bool + */ + private function isRequestAllowed() + { + return $this->getRequest()->isAjax() && $this->getRequest()->isPost(); + } } diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php index 3bbda65cb4cf6..271e3713034d0 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Backup\Controller\Adminhtml\Index; -class Index extends \Magento\Backup\Controller\Adminhtml\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Backup\Controller\Adminhtml\Index implements HttpGetActionInterface { /** * Backup list action diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php index 04292d2759093..90657fc2490ba 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php @@ -49,13 +49,13 @@ public function execute() $resultData->setIsSuccess(true); if ($allBackupsDeleted) { - $this->messageManager->addSuccess(__('You deleted the selected backup(s).')); + $this->messageManager->addSuccessMessage(__('You deleted the selected backup(s).')); } else { throw new \Exception($deleteFailMessage); } } catch (\Exception $e) { $resultData->setIsSuccess(false); - $this->messageManager->addError($deleteFailMessage); + $this->messageManager->addErrorMessage($deleteFailMessage); } return $this->_redirect('backup/*/index'); diff --git a/app/code/Magento/Backup/Helper/Data.php b/app/code/Magento/Backup/Helper/Data.php index 3d60bf9d9c9cf..b0bc292ffe926 100644 --- a/app/code/Magento/Backup/Helper/Data.php +++ b/app/code/Magento/Backup/Helper/Data.php @@ -110,7 +110,7 @@ public function getBackupsDir() public function getExtensionByType($type) { $extensions = $this->getExtensions(); - return isset($extensions[$type]) ? $extensions[$type] : ''; + return $extensions[$type] ?? ''; } /** diff --git a/app/code/Magento/Backup/Model/BackupFactory.php b/app/code/Magento/Backup/Model/BackupFactory.php index 28b0a7baf43cb..f88b410a2371b 100644 --- a/app/code/Magento/Backup/Model/BackupFactory.php +++ b/app/code/Magento/Backup/Model/BackupFactory.php @@ -39,8 +39,8 @@ public function __construct(\Magento\Framework\ObjectManagerInterface $objectMan */ public function create($timestamp, $type) { - $fsCollection = $this->_objectManager->get(\Magento\Backup\Model\Fs\Collection::class); - $backupInstance = $this->_objectManager->get(\Magento\Backup\Model\Backup::class); + $fsCollection = $this->_objectManager->create(\Magento\Backup\Model\Fs\Collection::class); + $backupInstance = $this->_objectManager->create(\Magento\Backup\Model\Backup::class); foreach ($fsCollection as $backup) { if ($backup->getTime() === (int) $timestamp && $backup->getType() === $type) { diff --git a/app/code/Magento/Backup/Model/Config/Backend/Cron.php b/app/code/Magento/Backup/Model/Config/Backend/Cron.php index 4855ef1129502..2f0e4069f0499 100644 --- a/app/code/Magento/Backup/Model/Config/Backend/Cron.php +++ b/app/code/Magento/Backup/Model/Config/Backend/Cron.php @@ -76,8 +76,8 @@ public function afterSave() if ($enabled) { $cronExprArray = [ - intval($time[1]), # Minute - intval($time[0]), # Hour + (int) $time[1], # Minute + (int) $time[0], # Hour $frequency == $frequencyMonthly ? '1' : '*', # Day of the Month '*', # Month of the Year $frequency == $frequencyWeekly ? '1' : '*', # Day of the Week diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php index 776141249f92b..8fbd5da1c9842 100644 --- a/app/code/Magento/Backup/Model/Db.php +++ b/app/code/Magento/Backup/Model/Db.php @@ -154,7 +154,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu if ($tableStatus->getDataLength() > self::BUFFER_LENGTH) { if ($tableStatus->getAvgRowLength() < self::BUFFER_LENGTH) { - $limit = floor(self::BUFFER_LENGTH / $tableStatus->getAvgRowLength()); + $limit = floor(self::BUFFER_LENGTH / max($tableStatus->getAvgRowLength(), 1)); $multiRowsLength = ceil($tableStatus->getRows() / $limit); } else { $limit = 1; @@ -173,6 +173,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu } } $backup->write($this->getResource()->getTableForeignKeysSql()); + $backup->write($this->getResource()->getTableTriggersSql()); $backup->write($this->getResource()->getFooter()); $this->getResource()->commitTransaction(); diff --git a/app/code/Magento/Backup/Model/Fs/Collection.php b/app/code/Magento/Backup/Model/Fs/Collection.php index 2d08ac04528ac..b17c17f7074fb 100644 --- a/app/code/Magento/Backup/Model/Fs/Collection.php +++ b/app/code/Magento/Backup/Model/Fs/Collection.php @@ -115,7 +115,8 @@ protected function _generateRow($filename) if (isset($row['display_name']) && $row['display_name'] == '') { $row['display_name'] = 'WebSetupWizard'; } - $row['id'] = $row['time'] . '_' . $row['type'] . (isset($row['display_name']) ? $row['display_name'] : ''); + $row['id'] = $row['time'] . '_' . $row['type'] + . (isset($row['display_name']) ? '_' . $row['display_name'] : ''); return $row; } } diff --git a/app/code/Magento/Backup/Model/ResourceModel/Db.php b/app/code/Magento/Backup/Model/ResourceModel/Db.php index 3fbaf44ebb063..6e7d6f9863f33 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Db.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Db.php @@ -114,6 +114,31 @@ public function getTableForeignKeysSql($tableName = null) return $fkScript; } + /** + * Return triggers for table(s). + * + * @param string|null $tableName + * @param bool $addDropIfExists + * @return string + */ + public function getTableTriggersSql($tableName = null, $addDropIfExists = true) + { + $triggerScript = ''; + if (!$tableName) { + $tables = $this->getTables(); + foreach ($tables as $table) { + $tableTriggerScript = $this->_resourceHelper->getTableTriggersSql($table, $addDropIfExists); + if (!empty($tableTriggerScript)) { + $triggerScript .= "\n" . $tableTriggerScript; + } + } + } else { + $triggerScript = $this->getTableTriggersSql($tableName, $addDropIfExists); + } + + return $triggerScript; + } + /** * Retrieve table status * diff --git a/app/code/Magento/Backup/Model/ResourceModel/Helper.php b/app/code/Magento/Backup/Model/ResourceModel/Helper.php index b5418865339c3..dace56fd60688 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Helper.php @@ -337,4 +337,40 @@ public function restoreTransactionIsolationLevel() { $this->getConnection()->query('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ'); } + + /** + * Get create script for triggers. + * + * @param string $tableName + * @param boolean $addDropIfExists + * @param boolean $stripDefiner + * @return string + */ + public function getTableTriggersSql($tableName, $addDropIfExists = false, $stripDefiner = true) + { + $script = "--\n-- Triggers structure for table `{$tableName}`\n--\n"; + $triggers = $this->getConnection()->query('SHOW TRIGGERS LIKE \''. $tableName . '\'')->fetchAll(); + + if (!$triggers) { + return ''; + } + foreach ($triggers as $trigger) { + if ($addDropIfExists) { + $script .= 'DROP TRIGGER IF EXISTS ' . $trigger['Trigger'] . ";\n"; + } + $script .= "delimiter ;;\n"; + + $triggerData = $this->getConnection()->query('SHOW CREATE TRIGGER '. $trigger['Trigger'])->fetch(); + if ($stripDefiner) { + $cleanedScript = preg_replace('/DEFINER=[^\s]*/', '', $triggerData['SQL Original Statement']); + $script .= $cleanedScript . "\n"; + } else { + $script .= $triggerData['SQL Original Statement'] . "\n"; + } + $script .= ";;\n"; + $script .= "delimiter ;\n"; + } + + return $script; + } } diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml new file mode 100644 index 0000000000000..89381112e6c66 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml @@ -0,0 +1,54 @@ +<?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="createSystemBackup"> + <arguments> + <argument name="backup" defaultValue="SystemBackup"/> + </arguments> + <click selector="{{AdminMainActionsSection.systemBackup}}" stepKey="clickCreateBackupButton"/> + <waitForElementVisible selector="{{AdminCreateBackupFormSection.backupNameField}}" stepKey="waitForForm"/> + <fillField selector="{{AdminCreateBackupFormSection.backupNameField}}" userInput="{{backup.name}}" stepKey="fillBackupName"/> + <click selector="{{AdminCreateBackupFormSection.ok}}" stepKey="clickOk"/> + <waitForElementNotVisible selector=".loading-mask" time="300" stepKey="waitForBackupProcess"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You created the system backup." stepKey="seeSuccessMessage"/> + <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{backup.name}}" stepKey="seeBackupInGrid"/> + <see selector="{{AdminGridTableSection.backupTypeByName(backup.name)}}" userInput="{{backup.type}}" stepKey="seeBackupType"/> + </actionGroup> + + <actionGroup name="createMediaBackup"> + <arguments> + <argument name="backup" defaultValue="MediaBackup"/> + </arguments> + <click selector="{{AdminMainActionsSection.mediaBackup}}" stepKey="clickCreateBackupButton"/> + <waitForElementVisible selector="{{AdminCreateBackupFormSection.backupNameField}}" stepKey="waitForForm"/> + <fillField selector="{{AdminCreateBackupFormSection.backupNameField}}" userInput="{{backup.name}}" stepKey="fillBackupName"/> + <click selector="{{AdminCreateBackupFormSection.ok}}" stepKey="clickOk"/> + <waitForPageLoad time="120" stepKey="waitForBackupProcess"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You created the database and media backup." stepKey="seeSuccessMessage"/> + <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{backup.name}}" stepKey="seeBackupInGrid"/> + <see selector="{{AdminGridTableSection.backupTypeByName(backup.name)}}" userInput="{{backup.type}}" stepKey="seeBackupType"/> + </actionGroup> + + <actionGroup name="createDatabaseBackup"> + <arguments> + <argument name="backup" defaultValue="DatabaseBackup"/> + </arguments> + <click selector="{{AdminMainActionsSection.databaseBackup}}" stepKey="clickCreateBackupButton"/> + <waitForElementVisible selector="{{AdminCreateBackupFormSection.backupNameField}}" stepKey="waitForForm"/> + <fillField selector="{{AdminCreateBackupFormSection.backupNameField}}" userInput="{{backup.name}}" stepKey="fillBackupName"/> + <click selector="{{AdminCreateBackupFormSection.ok}}" stepKey="clickOk"/> + <waitForPageLoad time="120" stepKey="waitForBackupProcess"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You created the database backup." stepKey="seeSuccessMessage"/> + <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{backup.name}}" stepKey="seeBackupInGrid"/> + <see selector="{{AdminGridTableSection.backupTypeByName(backup.name)}}" userInput="{{backup.type}}" stepKey="seeBackupType"/> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml new file mode 100644 index 0000000000000..4f34f24c3a806 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml @@ -0,0 +1,25 @@ +<?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="deleteBackup"> + <arguments> + <argument name="backup"/> + </arguments> + <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{backup.name}}" stepKey="seeBackupInGrid"/> + <click selector="{{AdminGridTableSection.backupRowCheckbox(backup.name)}}" stepKey="selectBackupRow"/> + <selectOption selector="{{AdminGridActionSection.actionSelect}}" userInput="Delete" stepKey="selectDeleteAction"/> + <click selector="{{AdminGridActionSection.submitButton}}" stepKey="clickSubmit"/> + <see selector="{{AdminConfirmationModalSection.message}}" userInput="Are you sure you want to delete the selected backup(s)?" stepKey="seeConfirmationModal"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="clickOkConfirmDelete"/> + <dontSee selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{backup.name}}" stepKey="dontSeeBackupInGrid"/> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml new file mode 100644 index 0000000000000..ae97351cafcaf --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml @@ -0,0 +1,23 @@ +<?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="SystemBackup" type="backup"> + <data key="name" unique="suffix">systemBackup</data> + <data key="type">System</data> + </entity> + <entity name="MediaBackup" type="backup"> + <data key="name" unique="suffix">mediaBackup</data> + <data key="type">Database and Media</data> + </entity> + <entity name="DatabaseBackup" type="backup"> + <data key="name" unique="suffix">databaseBackup</data> + <data key="type">Database</data> + </entity> +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/BundleImportExport/LICENSE.txt b/app/code/Magento/Backup/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/BundleImportExport/LICENSE.txt rename to app/code/Magento/Backup/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/LICENSE_AFL.txt b/app/code/Magento/Backup/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/LICENSE_AFL.txt rename to app/code/Magento/Backup/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml b/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml new file mode 100644 index 0000000000000..aad29e6e6d566 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml @@ -0,0 +1,16 @@ +<?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="BackupIndexPage" url="/backup/index/" area="admin" module="Magento_Backup"> + <section name="AdminMainActionsSection"/> + <section name="AdminGridTableSection"/> + <section name="AdminCreateBackupFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backup/Test/Mftf/README.md b/app/code/Magento/Backup/Test/Mftf/README.md new file mode 100644 index 0000000000000..6951acdf41400 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Backup Functional Tests + +The Functional Test Module for **Magento Backup** module. diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.xml new file mode 100644 index 0000000000000..af88146bcfb4b --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.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="AdminCreateBackupFormSection"> + <element name="backupNameField" type="input" selector="input[name='backup_name']"/> + <element name="maintenanceModeCheckbox" type="checkbox" selector="input[name='maintenance_mode']"/> + <element name="excludeMediaCheckbox" type="checkbox" selector="input[name='exclude_media']"/> + <element name="ok" type="button" selector=".modal-header button.primary"/> + <element name="cancel" type="button" selector=".modal-header button.cancel"/> + </section> +</sections> diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.xml new file mode 100644 index 0000000000000..cca6b428a2820 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.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="AdminGridActionSection"> + <element name="actionSelect" type="select" selector="#backupsGrid_massaction-select"/> + <element name="submitButton" type="button" selector="#backupsGrid_massaction button[title='Submit']"/> + </section> +</sections> diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.xml new file mode 100644 index 0000000000000..72fb51684931f --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.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="AdminGridTableSection"> + <element name="backupNameColumn" type="text" selector="table.data-grid td[data-column='display_name']"/> + <element name="backupTypeByName" type="text" selector="//table//td[contains(., '{{name}}')]/parent::tr/td[@data-column='type']" parameterized="true"/> + <element name="backupRowCheckbox" type="checkbox" selector="//table//td[contains(., '{{name}}')]/parent::tr/td[@data-column='massaction']//input" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.xml new file mode 100644 index 0000000000000..11ba79f32b5db --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.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="AdminMainActionsSection"> + <element name="systemBackup" type="button" selector="button[data-ui-id*='createsnapshotbutton']"/> + <element name="mediaBackup" type="button" selector="button[data-ui-id*='createmediabackupbutton']"/> + <element name="databaseBackup" type="button" selector="button.database-backup[data-ui-id*='createbutton']"/> + </section> +</sections> diff --git a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml new file mode 100644 index 0000000000000..496bb79343092 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.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="AdminCreateAndDeleteBackupsTest"> + <annotations> + <features value="Backup"/> + <stories value="Create and delete backups"/> + <title value="Create and delete backups"/> + <description value="An admin user can create a backup of each type and delete each backup."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94176"/> + <group value="backup"/> + <skip> + <issueId value="MQE-1187"/> + <issueId value="DEVOPS-3512"/> + </skip> + </annotations> + + <!--Login to admin area--> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + + <!--Go to backup index page--> + <amOnPage url="{{BackupIndexPage.url}}" stepKey="goToBackupPage"/> + <waitForPageLoad stepKey="waitForBackupPage"/> + + <!--Create system backup--> + <actionGroup ref="createSystemBackup" stepKey="createSystemBackup"/> + + <!--Create database/media backup--> + <actionGroup ref="createMediaBackup" stepKey="createMediaBackup"/> + + <!--Create database backup--> + <actionGroup ref="createDatabaseBackup" stepKey="createDatabaseBackup"/> + + <!--Delete system backup--> + <actionGroup ref="deleteBackup" stepKey="deleteSystemBackup"> + <argument name="backup" value="SystemBackup"/> + </actionGroup> + + <!--Delete database/media backup--> + <actionGroup ref="deleteBackup" stepKey="deleteMediaBackup"> + <argument name="backup" value="MediaBackup"/> + </actionGroup> + + <!--Delete database backup--> + <actionGroup ref="deleteBackup" stepKey="deleteDatabaseBackup"> + <argument name="backup" value="DatabaseBackup"/> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/CreateTest.php b/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/CreateTest.php new file mode 100644 index 0000000000000..80477d9f6f9e4 --- /dev/null +++ b/app/code/Magento/Backup/Test/Unit/Controller/Adminhtml/Index/CreateTest.php @@ -0,0 +1,240 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backup\Test\Unit\Controller\Adminhtml\Index; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * Tests \Magento\Backup\Controller\Adminhtml\Index\Create class. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CreateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Backend\App\Action\Context + */ + private $context; + + /** + * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseMock; + + /** + * @var \Magento\Backup\Model\Backup|\PHPUnit_Framework_MockObject_MockObject + */ + private $backupModelMock; + + /** + * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataBackendHelperMock; + + /** + * @var \Magento\Backup\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataBackupHelperMock; + + /** + * @var \Magento\Framework\App\Response\Http\FileFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileFactoryMock; + + /** + * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionMock; + + /** + * @var \Magento\Framework\App\MaintenanceMode|\PHPUnit_Framework_MockObject_MockObject + */ + private $maintenanceModeMock; + + /** + * @var \Magento\Framework\Backup\Factory|\PHPUnit_Framework_MockObject_MockObject + */ + private $backupFactoryMock; + + /** + * @var \Magento\Backup\Controller\Adminhtml\Index\Create + */ + private $createController; + + public function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + ->getMock(); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + ->disableOriginalConstructor() + ->setMethods(['isAjax', 'isPost', 'getParam']) + ->getMock(); + $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http::class) + ->disableOriginalConstructor() + ->setMethods(['representJson', 'setRedirect']) + ->getMock(); + $this->sessionMock = $this->getMockBuilder(\Magento\Backend\Model\Session::class) + ->disableOriginalConstructor() + ->getMock(); + $this->backupFactoryMock = $this->getMockBuilder(\Magento\Framework\Backup\Factory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->backupModelMock = $this->getMockBuilder(\Magento\Backup\Model\Backup::class) + ->disableOriginalConstructor() + ->setMethods(['setBackupExtension', 'setTime', 'setBackupsDir', 'setName', 'create']) + ->getMock(); + $this->dataBackendHelperMock = $this->getMockBuilder(\Magento\Backend\Helper\Data::class) + ->disableOriginalConstructor() + ->setMethods(['getUrl']) + ->getMock(); + $this->dataBackupHelperMock = $this->getMockBuilder(\Magento\Backup\Helper\Data::class) + ->disableOriginalConstructor() + ->setMethods(['getExtensionByType', 'getBackupsDir']) + ->getMock(); + $this->maintenanceModeMock = $this->getMockBuilder(\Magento\Framework\App\MaintenanceMode::class) + ->disableOriginalConstructor() + ->setMethods(['set']) + ->getMock(); + $this->fileFactoryMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManager = new ObjectManager($this); + $this->context = $this->objectManager->getObject( + \Magento\Backend\App\Action\Context::class, + [ + 'objectManager' => $this->objectManagerMock, + 'request' => $this->requestMock, + 'response' => $this->responseMock, + 'session' => $this->sessionMock, + 'helper' => $this->dataBackendHelperMock, + 'maintenanceMode' => $this->maintenanceModeMock, + ] + ); + $this->createController = $this->objectManager->getObject( + \Magento\Backup\Controller\Adminhtml\Index\Create::class, + [ + 'context' => $this->context, + 'backupFactory' => $this->backupFactoryMock, + 'fileFactory' => $this->fileFactoryMock, + ] + ); + } + + /** + * @covers \Magento\Backup\Controller\Adminhtml\Index\Create::execute + * @return void + */ + public function testExecuteNotPost() + { + $redirectUrl = '*/*/index'; + $redirectUrlBackup = 'backup/index/index'; + + $this->requestMock->expects($this->any()) + ->method('isAjax') + ->willReturn(true); + $this->requestMock->expects($this->any()) + ->method('isPost') + ->willReturn(false); + $this->dataBackendHelperMock->expects($this->any()) + ->method('getUrl') + ->with($redirectUrl, []) + ->willReturn($redirectUrlBackup); + $this->responseMock->expects($this->any()) + ->method('setRedirect') + ->with($redirectUrlBackup) + ->willReturnSelf(); + + $this->assertSame($this->responseMock, $this->createController->execute()); + } + + /** + * @covers \Magento\Backup\Controller\Adminhtml\Index\Create::execute + * @return void + */ + public function testExecutePermission() + { + $redirectUrl = '*/*/index'; + $redirectUrlBackup = 'backup/index/index'; + $backupType = 'db'; + $backupName = 'backup1'; + $response = '{"redirect_url":"backup\/index\/index"}'; + + $this->requestMock->expects($this->any()) + ->method('isAjax') + ->willReturn(true); + $this->requestMock->expects($this->any()) + ->method('isPost') + ->willReturn(true); + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['type', null, $backupType], + ['backup_name', null, $backupName], + ]); + $this->dataBackendHelperMock->expects($this->any()) + ->method('getUrl') + ->with($redirectUrl, []) + ->willReturn($redirectUrlBackup); + $this->responseMock->expects($this->any()) + ->method('representJson') + ->with($response) + ->willReturnSelf(); + $this->maintenanceModeMock->expects($this->any()) + ->method('set') + ->with(true) + ->willReturn(false); + $this->backupFactoryMock->expects($this->any()) + ->method('create') + ->with($backupType) + ->willReturn($this->backupModelMock); + $this->backupModelMock->expects($this->any()) + ->method('setBackupExtension') + ->with($backupType) + ->willReturnSelf(); + $this->backupModelMock->expects($this->any()) + ->method('setBackupsDir') + ->willReturnSelf(); + $this->backupModelMock->expects($this->any()) + ->method('setTime') + ->willReturnSelf(); + $this->backupModelMock->expects($this->any()) + ->method('setName') + ->with($backupName) + ->willReturnSelf(); + $this->backupModelMock->expects($this->once()) + ->method('create') + ->willReturnSelf(); + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->with(\Magento\Backup\Helper\Data::class) + ->willReturn($this->dataBackupHelperMock); + $this->dataBackupHelperMock->expects($this->any()) + ->method('getExtensionByType') + ->with($backupType) + ->willReturn($backupType); + $this->dataBackupHelperMock->expects($this->any()) + ->method('getBackupsDir') + ->willReturn('dir'); + + $this->assertNull($this->createController->execute()); + } +} diff --git a/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php b/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php index 629028bfd6f15..abf5e63276afa 100644 --- a/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php +++ b/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php @@ -56,7 +56,7 @@ protected function setUp() $this->_objectManager->expects( $this->at(0) )->method( - 'get' + 'create' )->with( \Magento\Backup\Model\Fs\Collection::class )->will( @@ -65,7 +65,7 @@ protected function setUp() $this->_objectManager->expects( $this->at(1) )->method( - 'get' + 'create' )->with( \Magento\Backup\Model\Backup::class )->will( diff --git a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php new file mode 100644 index 0000000000000..0cab5f0ad1e99 --- /dev/null +++ b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php @@ -0,0 +1,243 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backup\Test\Unit\Model; + +use Magento\Backup\Model\Db; +use Magento\Backup\Model\ResourceModel\Db as DbResource; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Backup\Db\BackupInterface; +use Magento\Framework\DataObject; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class DbTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Db + */ + private $dbModel; + + /** + * @var DbResource|\PHPUnit_Framework_MockObject_MockObject + */ + private $dbResourceMock; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionResourceMock; + + protected function setUp() + { + $this->dbResourceMock = $this->getMockBuilder(DbResource::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionResourceMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new ObjectManager($this); + $this->dbModel = $this->objectManager->getObject( + Db::class, + [ + 'resourceDb' => $this->dbResourceMock, + 'resource' => $this->connectionResourceMock + ] + ); + } + + public function testGetResource() + { + self::assertEquals($this->dbResourceMock, $this->dbModel->getResource()); + } + + public function testGetTables() + { + $tables = []; + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + + self::assertEquals($tables, $this->dbModel->getTables()); + } + + public function testGetTableCreateScript() + { + $tableName = 'some_table'; + $script = 'script'; + $this->dbResourceMock->expects($this->once()) + ->method('getTableCreateScript') + ->with($tableName, false) + ->willReturn($script); + + self::assertEquals($script, $this->dbModel->getTableCreateScript($tableName, false)); + } + + public function testGetTableDataDump() + { + $tableName = 'some_table'; + $dump = 'dump'; + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataDump') + ->with($tableName) + ->willReturn($dump); + + self::assertEquals($dump, $this->dbModel->getTableDataDump($tableName)); + } + + public function testGetHeader() + { + $header = 'header'; + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + + self::assertEquals($header, $this->dbModel->getHeader()); + } + + public function testGetFooter() + { + $footer = 'footer'; + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + + self::assertEquals($footer, $this->dbModel->getFooter()); + } + + public function testRenderSql() + { + $header = 'header'; + $script = 'script'; + $tableName = 'some_table'; + $tables = [$tableName, $tableName]; + $dump = 'dump'; + $footer = 'footer'; + + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->exactly(2)) + ->method('getTableCreateScript') + ->with($tableName, true) + ->willReturn($script); + $this->dbResourceMock->expects($this->exactly(2)) + ->method('getTableDataDump') + ->with($tableName) + ->willReturn($dump); + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + + self::assertEquals( + $header . $script . $dump . $script . $dump . $footer, + $this->dbModel->renderSql() + ); + } + + public function testCreateBackup() + { + /** @var BackupInterface|\PHPUnit_Framework_MockObject_MockObject $backupMock */ + $backupMock = $this->getMockBuilder(BackupInterface::class)->getMock(); + /** @var DataObject $tableStatus */ + $tableStatus = new DataObject(); + + $tableName = 'some_table'; + $tables = [$tableName]; + $header = 'header'; + $footer = 'footer'; + $dropSql = 'drop_sql'; + $createSql = 'create_sql'; + $beforeSql = 'before_sql'; + $afterSql = 'after_sql'; + $dataSql = 'data_sql'; + $foreignKeysSql = 'foreign_keys'; + $triggersSql = 'triggers_sql'; + $rowsCount = 2; + $dataLength = 1; + + $this->dbResourceMock->expects($this->once()) + ->method('beginTransaction'); + $this->dbResourceMock->expects($this->once()) + ->method('commitTransaction'); + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDropSql') + ->willReturn($dropSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableCreateSql') + ->with($tableName, false) + ->willReturn($createSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataBeforeSql') + ->with($tableName) + ->willReturn($beforeSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataAfterSql') + ->with($tableName) + ->willReturn($afterSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataSql') + ->with($tableName, $rowsCount, 0) + ->willReturn($dataSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableStatus') + ->with($tableName) + ->willReturn($tableStatus); + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($createSql); + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->once()) + ->method('getTableHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + $this->dbResourceMock->expects($this->once()) + ->method('getTableForeignKeysSql') + ->willReturn($foreignKeysSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableTriggersSql') + ->willReturn($triggersSql); + $backupMock->expects($this->once()) + ->method('open'); + $backupMock->expects($this->once()) + ->method('close'); + + $tableStatus->setRows($rowsCount); + $tableStatus->setDataLength($dataLength); + + $backupMock->expects($this->any()) + ->method('write') + ->withConsecutive( + [$this->equalTo($header)], + [$this->equalTo($header . $dropSql . "\n")], + [$this->equalTo($createSql . "\n")], + [$this->equalTo($beforeSql)], + [$this->equalTo($dataSql)], + [$this->equalTo($afterSql)], + [$this->equalTo($foreignKeysSql)], + [$this->equalTo($triggersSql)], + [$this->equalTo($footer)] + ); + + $this->dbModel->createBackup($backupMock); + } +} diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index 5ae2ad06b7d01..81225430b0fc8 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -5,14 +5,13 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/framework": "100.3.*", - "magento/module-backend": "100.3.*", - "magento/module-cron": "100.3.*", - "magento/module-store": "100.3.*" + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-cron": "*", + "magento/module-store": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Backup/etc/module.xml b/app/code/Magento/Backup/etc/module.xml index 667ec9d8a7461..3e9906a5ecd74 100644 --- a/app/code/Magento/Backup/etc/module.xml +++ b/app/code/Magento/Backup/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Backup" setup_version="2.0.0"> + <module name="Magento_Backup" > <sequence> <module name="Magento_Store"/> </sequence> diff --git a/app/code/Magento/Braintree/Block/Form.php b/app/code/Magento/Braintree/Block/Form.php index f6cf62f5a2131..8a727ae87262f 100644 --- a/app/code/Magento/Braintree/Block/Form.php +++ b/app/code/Magento/Braintree/Block/Form.php @@ -9,7 +9,6 @@ use Magento\Braintree\Gateway\Config\Config as GatewayConfig; use Magento\Braintree\Model\Adminhtml\Source\CcType; use Magento\Braintree\Model\Ui\ConfigProvider; -use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Element\Template\Context; use Magento\Payment\Block\Form\Cc; use Magento\Payment\Helper\Data; @@ -21,7 +20,6 @@ */ class Form extends Cc { - /** * @var Quote */ @@ -48,6 +46,7 @@ class Form extends Cc * @param Quote $sessionQuote * @param GatewayConfig $gatewayConfig * @param CcType $ccType + * @param Data $paymentDataHelper * @param array $data */ public function __construct( @@ -56,12 +55,14 @@ public function __construct( Quote $sessionQuote, GatewayConfig $gatewayConfig, CcType $ccType, + Data $paymentDataHelper, array $data = [] ) { parent::__construct($context, $paymentConfig, $data); $this->sessionQuote = $sessionQuote; $this->gatewayConfig = $gatewayConfig; $this->ccType = $ccType; + $this->paymentDataHelper = $paymentDataHelper; } /** @@ -81,7 +82,7 @@ public function getCcAvailableTypes() */ public function useCvv() { - return $this->gatewayConfig->isCvvEnabled(); + return $this->gatewayConfig->isCvvEnabled($this->sessionQuote->getStoreId()); } /** @@ -90,9 +91,8 @@ public function useCvv() */ public function isVaultEnabled() { - $storeId = $this->_storeManager->getStore()->getId(); $vaultPayment = $this->getVaultPayment(); - return $vaultPayment->isActive($storeId); + return $vaultPayment->isActive($this->sessionQuote->getStoreId()); } /** @@ -102,7 +102,10 @@ public function isVaultEnabled() private function getConfiguredCardTypes() { $types = $this->ccType->getCcTypeLabelMap(); - $configCardTypes = array_fill_keys($this->gatewayConfig->getAvailableCardTypes(), ''); + $configCardTypes = array_fill_keys( + $this->gatewayConfig->getAvailableCardTypes($this->sessionQuote->getStoreId()), + '' + ); return array_intersect_key($types, $configCardTypes); } @@ -116,7 +119,11 @@ private function getConfiguredCardTypes() private function filterCardTypesForCountry(array $configCardTypes, $countryId) { $filtered = $configCardTypes; - $countryCardTypes = $this->gatewayConfig->getCountryAvailableCardTypes($countryId); + $countryCardTypes = $this->gatewayConfig->getCountryAvailableCardTypes( + $countryId, + $this->sessionQuote->getStoreId() + ); + // filter card types only if specific card types are set for country if (!empty($countryCardTypes)) { $availableTypes = array_fill_keys($countryCardTypes, ''); @@ -131,19 +138,6 @@ private function filterCardTypesForCountry(array $configCardTypes, $countryId) */ private function getVaultPayment() { - return $this->getPaymentDataHelper()->getMethodInstance(ConfigProvider::CC_VAULT_CODE); - } - - /** - * Get payment data helper instance - * @return Data - * @deprecated 100.1.0 - */ - private function getPaymentDataHelper() - { - if ($this->paymentDataHelper === null) { - $this->paymentDataHelper = ObjectManager::getInstance()->get(Data::class); - } - return $this->paymentDataHelper; + return $this->paymentDataHelper->getMethodInstance(ConfigProvider::CC_VAULT_CODE); } } diff --git a/app/code/Magento/Braintree/Block/Payment.php b/app/code/Magento/Braintree/Block/Payment.php index 8e05856d9b57a..1ba2f862e2fe5 100644 --- a/app/code/Magento/Braintree/Block/Payment.php +++ b/app/code/Magento/Braintree/Block/Payment.php @@ -48,6 +48,10 @@ public function getPaymentConfig() $payment = $this->config->getConfig()['payment']; $config = $payment[$this->getCode()]; $config['code'] = $this->getCode(); + $config['clientTokenUrl'] = $this->_urlBuilder->getUrl( + 'braintree/payment/getClientToken', + ['_secure' => true] + ); return json_encode($config, JSON_UNESCAPED_SLASHES); } diff --git a/app/code/Magento/Braintree/Controller/Adminhtml/Payment/GetClientToken.php b/app/code/Magento/Braintree/Controller/Adminhtml/Payment/GetClientToken.php new file mode 100644 index 0000000000000..4b9721a693f08 --- /dev/null +++ b/app/code/Magento/Braintree/Controller/Adminhtml/Payment/GetClientToken.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Controller\Adminhtml\Payment; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\Session\Quote; +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\Request\PaymentDataBuilder; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Framework\Controller\ResultFactory; + +/** + * Retrieves client token. + */ +class GetClientToken extends Action +{ + const ADMIN_RESOURCE = 'Magento_Braintree::get_client_token'; + + /** + * @var Config + */ + private $config; + + /** + * @var BraintreeAdapterFactory + */ + private $adapterFactory; + + /** + * @var Quote + */ + private $quoteSession; + + /** + * @param Context $context + * @param Config $config + * @param BraintreeAdapterFactory $adapterFactory + * @param Quote $quoteSession + */ + public function __construct( + Context $context, + Config $config, + BraintreeAdapterFactory $adapterFactory, + Quote $quoteSession + ) { + parent::__construct($context); + $this->config = $config; + $this->adapterFactory = $adapterFactory; + $this->quoteSession = $quoteSession; + } + + /** + * @inheritdoc + */ + public function execute() + { + $params = []; + $response = $this->resultFactory->create(ResultFactory::TYPE_JSON); + + $storeId = $this->quoteSession->getStoreId(); + $merchantAccountId = $this->config->getMerchantAccountId($storeId); + if (!empty($merchantAccountId)) { + $params[PaymentDataBuilder::MERCHANT_ACCOUNT_ID] = $merchantAccountId; + } + + $clientToken = $this->adapterFactory->create($storeId) + ->generate($params); + $response->setData(['clientToken' => $clientToken]); + + return $response; + } +} diff --git a/app/code/Magento/Braintree/Controller/Payment/GetNonce.php b/app/code/Magento/Braintree/Controller/Payment/GetNonce.php index aecde869ca196..f8b152ded1556 100644 --- a/app/code/Magento/Braintree/Controller/Payment/GetNonce.php +++ b/app/code/Magento/Braintree/Controller/Payment/GetNonce.php @@ -62,7 +62,10 @@ public function execute() try { $publicHash = $this->getRequest()->getParam('public_hash'); $customerId = $this->session->getCustomerId(); - $result = $this->command->execute(['public_hash' => $publicHash, 'customer_id' => $customerId])->get(); + $result = $this->command->execute( + ['public_hash' => $publicHash, 'customer_id' => $customerId, 'store_id' => $this->session->getStoreId()] + ) + ->get(); $response->setData(['paymentMethodNonce' => $result['paymentMethodNonce']]); } catch (\Exception $e) { $this->logger->critical($e); diff --git a/app/code/Magento/Braintree/Controller/Paypal/AbstractAction.php b/app/code/Magento/Braintree/Controller/Paypal/AbstractAction.php index d6a81eefc6fdb..e0ed996019576 100644 --- a/app/code/Magento/Braintree/Controller/Paypal/AbstractAction.php +++ b/app/code/Magento/Braintree/Controller/Paypal/AbstractAction.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Controller\Paypal; use Magento\Checkout\Model\Session; @@ -73,7 +74,7 @@ public function dispatch(RequestInterface $request) protected function validateQuote($quote) { if (!$quote || !$quote->getItemsCount()) { - throw new \InvalidArgumentException(__('We can\'t initialize checkout.')); + throw new \InvalidArgumentException(__('Checkout failed to initialize. Verify and try again.')); } } } diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php index 4576e3b033df8..ca252aabe54a9 100644 --- a/app/code/Magento/Braintree/Controller/Paypal/Review.php +++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Controller\Paypal; use Magento\Checkout\Model\Session; @@ -66,7 +67,7 @@ public function execute() $quote ); } elseif (!$quote->getPayment()->getAdditionalInformation(self::$paymentMethodNonce)) { - throw new LocalizedException(__('We can\'t initialize checkout.')); + throw new LocalizedException(__('Checkout failed to initialize. Verify and try again.')); } /** @var \Magento\Framework\View\Result\Page $resultPage */ diff --git a/app/code/Magento/Braintree/Gateway/Command/CaptureStrategyCommand.php b/app/code/Magento/Braintree/Gateway/Command/CaptureStrategyCommand.php index f972eecf9f92d..de439aad2defd 100644 --- a/app/code/Magento/Braintree/Gateway/Command/CaptureStrategyCommand.php +++ b/app/code/Magento/Braintree/Gateway/Command/CaptureStrategyCommand.php @@ -6,18 +6,19 @@ namespace Magento\Braintree\Gateway\Command; use Braintree\Transaction; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Braintree\Model\Adapter\BraintreeSearchAdapter; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Payment\Gateway\Command; use Magento\Payment\Gateway\Command\CommandPoolInterface; use Magento\Payment\Gateway\CommandInterface; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Helper\ContextHelper; -use Magento\Braintree\Gateway\Helper\SubjectReader; use Magento\Sales\Api\Data\OrderPaymentInterface; -use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Api\Data\TransactionInterface; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; /** * Class CaptureStrategyCommand @@ -66,9 +67,9 @@ class CaptureStrategyCommand implements CommandInterface private $subjectReader; /** - * @var BraintreeAdapter + * @var BraintreeAdapterFactory */ - private $braintreeAdapter; + private $braintreeAdapterFactory; /** * @var BraintreeSearchAdapter @@ -83,7 +84,7 @@ class CaptureStrategyCommand implements CommandInterface * @param FilterBuilder $filterBuilder * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param SubjectReader $subjectReader - * @param BraintreeAdapter $braintreeAdapter + * @param BraintreeAdapterFactory $braintreeAdapterFactory, * @param BraintreeSearchAdapter $braintreeSearchAdapter */ public function __construct( @@ -92,7 +93,7 @@ public function __construct( FilterBuilder $filterBuilder, SearchCriteriaBuilder $searchCriteriaBuilder, SubjectReader $subjectReader, - BraintreeAdapter $braintreeAdapter, + BraintreeAdapterFactory $braintreeAdapterFactory, BraintreeSearchAdapter $braintreeSearchAdapter ) { $this->commandPool = $commandPool; @@ -100,7 +101,7 @@ public function __construct( $this->filterBuilder = $filterBuilder; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->subjectReader = $subjectReader; - $this->braintreeAdapter = $braintreeAdapter; + $this->braintreeAdapterFactory = $braintreeAdapterFactory; $this->braintreeSearchAdapter = $braintreeSearchAdapter; } @@ -112,29 +113,29 @@ public function execute(array $commandSubject) /** @var \Magento\Payment\Gateway\Data\PaymentDataObjectInterface $paymentDO */ $paymentDO = $this->subjectReader->readPayment($commandSubject); - /** @var \Magento\Sales\Api\Data\OrderPaymentInterface $paymentInfo */ - $paymentInfo = $paymentDO->getPayment(); - ContextHelper::assertOrderPayment($paymentInfo); - - $command = $this->getCommand($paymentInfo); + $command = $this->getCommand($paymentDO); $this->commandPool->get($command)->execute($commandSubject); } /** - * Get execution command name - * @param OrderPaymentInterface $payment + * Get execution command name. + * + * @param PaymentDataObjectInterface $paymentDO * @return string */ - private function getCommand(OrderPaymentInterface $payment) + private function getCommand(PaymentDataObjectInterface $paymentDO) { - // if auth transaction is not exists execute authorize&capture command + $payment = $paymentDO->getPayment(); + ContextHelper::assertOrderPayment($payment); + + // if auth transaction does not exist then execute authorize&capture command $existsCapture = $this->isExistsCaptureTransaction($payment); if (!$payment->getAuthorizationTransaction() && !$existsCapture) { return self::SALE; } // do capture for authorization transaction - if (!$existsCapture && !$this->isExpiredAuthorization($payment)) { + if (!$existsCapture && !$this->isExpiredAuthorization($payment, $paymentDO->getOrder())) { return self::CAPTURE; } @@ -143,12 +144,16 @@ private function getCommand(OrderPaymentInterface $payment) } /** + * Checks if authorization transaction does not expired yet. + * * @param OrderPaymentInterface $payment - * @return boolean + * @param OrderAdapterInterface $orderAdapter + * @return bool */ - private function isExpiredAuthorization(OrderPaymentInterface $payment) + private function isExpiredAuthorization(OrderPaymentInterface $payment, OrderAdapterInterface $orderAdapter) { - $collection = $this->braintreeAdapter->search( + $adapter = $this->braintreeAdapterFactory->create($orderAdapter->getStoreId()); + $collection = $adapter->search( [ $this->braintreeSearchAdapter->id()->is($payment->getLastTransId()), $this->braintreeSearchAdapter->status()->is(Transaction::AUTHORIZATION_EXPIRED) diff --git a/app/code/Magento/Braintree/Gateway/Command/GetPaymentNonceCommand.php b/app/code/Magento/Braintree/Gateway/Command/GetPaymentNonceCommand.php index 91c9f6c14bb5d..672aa02a0db6d 100644 --- a/app/code/Magento/Braintree/Gateway/Command/GetPaymentNonceCommand.php +++ b/app/code/Magento/Braintree/Gateway/Command/GetPaymentNonceCommand.php @@ -6,11 +6,9 @@ namespace Magento\Braintree\Gateway\Command; -use Exception; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; -use Magento\Payment\Gateway\Command; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Payment\Gateway\Command\Result\ArrayResultFactory; use Magento\Payment\Gateway\CommandInterface; use Magento\Vault\Api\PaymentTokenManagementInterface; @@ -27,9 +25,9 @@ class GetPaymentNonceCommand implements CommandInterface private $tokenManagement; /** - * @var BraintreeAdapter + * @var BraintreeAdapterFactory */ - private $adapter; + private $adapterFactory; /** * @var ArrayResultFactory @@ -48,20 +46,20 @@ class GetPaymentNonceCommand implements CommandInterface /** * @param PaymentTokenManagementInterface $tokenManagement - * @param BraintreeAdapter $adapter + * @param BraintreeAdapterFactory $adapterFactory * @param ArrayResultFactory $resultFactory * @param SubjectReader $subjectReader * @param PaymentNonceResponseValidator $responseValidator */ public function __construct( PaymentTokenManagementInterface $tokenManagement, - BraintreeAdapter $adapter, + BraintreeAdapterFactory $adapterFactory, ArrayResultFactory $resultFactory, SubjectReader $subjectReader, PaymentNonceResponseValidator $responseValidator ) { $this->tokenManagement = $tokenManagement; - $this->adapter = $adapter; + $this->adapterFactory = $adapterFactory; $this->resultFactory = $resultFactory; $this->subjectReader = $subjectReader; $this->responseValidator = $responseValidator; @@ -77,14 +75,16 @@ public function execute(array $commandSubject) $customerId = $this->subjectReader->readCustomerId($commandSubject); $paymentToken = $this->tokenManagement->getByPublicHash($publicHash, $customerId); if (!$paymentToken) { - throw new Exception('No available payment tokens'); + throw new \Exception('No available payment tokens'); } - $data = $this->adapter->createNonce($paymentToken->getGatewayToken()); + $storeId = $this->subjectReader->readStoreId($commandSubject); + $data = $this->adapterFactory->create($storeId) + ->createNonce($paymentToken->getGatewayToken()); $result = $this->responseValidator->validate(['response' => ['object' => $data]]); if (!$result->isValid()) { - throw new Exception(__(implode("\n", $result->getFailsDescription()))); + throw new \Exception(__(implode("\n", $result->getFailsDescription()))); } return $this->resultFactory->create(['array' => ['paymentMethodNonce' => $data->paymentMethodNonce->nonce]]); diff --git a/app/code/Magento/Braintree/Gateway/Config/CanVoidHandler.php b/app/code/Magento/Braintree/Gateway/Config/CanVoidHandler.php index 9dd52dc91afb9..0466216bdb7d2 100644 --- a/app/code/Magento/Braintree/Gateway/Config/CanVoidHandler.php +++ b/app/code/Magento/Braintree/Gateway/Config/CanVoidHandler.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Config\ValueHandlerInterface; use Magento\Sales\Model\Order\Payment; diff --git a/app/code/Magento/Braintree/Gateway/Config/Config.php b/app/code/Magento/Braintree/Gateway/Config/Config.php index 7badd2b87ac34..2089a9646ae94 100644 --- a/app/code/Magento/Braintree/Gateway/Config/Config.php +++ b/app/code/Magento/Braintree/Gateway/Config/Config.php @@ -68,11 +68,12 @@ public function __construct( /** * Return the country specific card type config * + * @param int|null $storeId * @return array */ - public function getCountrySpecificCardTypeConfig() + public function getCountrySpecificCardTypeConfig($storeId = null) { - $countryCardTypes = $this->getValue(self::KEY_COUNTRY_CREDIT_CARD); + $countryCardTypes = $this->getValue(self::KEY_COUNTRY_CREDIT_CARD, $storeId); if (!$countryCardTypes) { return []; } @@ -83,11 +84,12 @@ public function getCountrySpecificCardTypeConfig() /** * Retrieve available credit card types * + * @param int|null $storeId * @return array */ - public function getAvailableCardTypes() + public function getAvailableCardTypes($storeId = null) { - $ccTypes = $this->getValue(self::KEY_CC_TYPES); + $ccTypes = $this->getValue(self::KEY_CC_TYPES, $storeId); return !empty($ccTypes) ? explode(',', $ccTypes) : []; } @@ -108,79 +110,110 @@ public function getCcTypesMapper() } /** - * Get list of card types available for country + * Gets list of card types available for country. + * * @param string $country + * @param int|null $storeId * @return array */ - public function getCountryAvailableCardTypes($country) + public function getCountryAvailableCardTypes($country, $storeId = null) { - $types = $this->getCountrySpecificCardTypeConfig(); + $types = $this->getCountrySpecificCardTypeConfig($storeId); return (!empty($types[$country])) ? $types[$country] : []; } /** - * Check if cvv field is enabled - * @return boolean + * Checks if cvv field is enabled. + * + * @param int|null $storeId + * @return bool */ - public function isCvvEnabled() + public function isCvvEnabled($storeId = null) { - return (bool) $this->getValue(self::KEY_USE_CVV); + return (bool) $this->getValue(self::KEY_USE_CVV, $storeId); } /** - * Check if 3d secure verification enabled + * Checks if 3d secure verification enabled. + * + * @param int|null $storeId * @return bool */ - public function isVerify3DSecure() + public function isVerify3DSecure($storeId = null) { - return (bool) $this->getValue(self::KEY_VERIFY_3DSECURE); + return (bool) $this->getValue(self::KEY_VERIFY_3DSECURE, $storeId); } /** - * Get threshold amount for 3d secure + * Gets threshold amount for 3d secure. + * + * @param int|null $storeId * @return float */ - public function getThresholdAmount() + public function getThresholdAmount($storeId = null) { - return (double) $this->getValue(self::KEY_THRESHOLD_AMOUNT); + return (double) $this->getValue(self::KEY_THRESHOLD_AMOUNT, $storeId); } /** - * Get list of specific countries for 3d secure + * Gets list of specific countries for 3d secure. + * + * @param int|null $storeId * @return array */ - public function get3DSecureSpecificCountries() + public function get3DSecureSpecificCountries($storeId = null) { - if ((int) $this->getValue(self::KEY_VERIFY_ALLOW_SPECIFIC) == self::VALUE_3DSECURE_ALL) { + if ((int) $this->getValue(self::KEY_VERIFY_ALLOW_SPECIFIC, $storeId) == self::VALUE_3DSECURE_ALL) { return []; } - return explode(',', $this->getValue(self::KEY_VERIFY_SPECIFIC)); + return explode(',', $this->getValue(self::KEY_VERIFY_SPECIFIC, $storeId)); + } + + /** + * Gets value of configured environment. + * Possible values: production or sandbox. + * + * @param int|null $storeId + * @return string + */ + public function getEnvironment($storeId = null) + { + return $this->getValue(Config::KEY_ENVIRONMENT, $storeId); } /** + * Gets Kount merchant ID. + * + * @param int|null $storeId * @return string */ - public function getEnvironment() + public function getKountMerchantId($storeId = null) { - return $this->getValue(Config::KEY_ENVIRONMENT); + return $this->getValue(Config::KEY_KOUNT_MERCHANT_ID, $storeId); } /** + * Gets merchant ID. + * + * @param int|null $storeId * @return string */ - public function getKountMerchantId() + public function getMerchantId($storeId = null) { - return $this->getValue(Config::KEY_KOUNT_MERCHANT_ID); + return $this->getValue(Config::KEY_MERCHANT_ID, $storeId); } /** + * Gets Merchant account ID. + * + * @param int|null $storeId * @return string */ - public function getMerchantId() + public function getMerchantAccountId($storeId = null) { - return $this->getValue(Config::KEY_MERCHANT_ID); + return $this->getValue(self::KEY_MERCHANT_ACCOUNT_ID, $storeId); } /** @@ -192,45 +225,42 @@ public function getSdkUrl() } /** + * Checks if fraud protection is enabled. + * + * @param int|null $storeId * @return bool */ - public function hasFraudProtection() + public function hasFraudProtection($storeId = null) { - return (bool) $this->getValue(Config::FRAUD_PROTECTION); + return (bool) $this->getValue(Config::FRAUD_PROTECTION, $storeId); } /** - * Get Payment configuration status + * Gets Payment configuration status. + * + * @param int|null $storeId * @return bool */ - public function isActive() + public function isActive($storeId = null) { - return (bool) $this->getValue(self::KEY_ACTIVE); + return (bool) $this->getValue(self::KEY_ACTIVE, $storeId); } /** - * Get list of configured dynamic descriptors + * Gets list of configured dynamic descriptors. + * + * @param int|null $storeId * @return array */ - public function getDynamicDescriptors() + public function getDynamicDescriptors($storeId = null) { $values = []; foreach (self::$dynamicDescriptorKeys as $key) { - $value = $this->getValue('descriptor_' . $key); + $value = $this->getValue('descriptor_' . $key, $storeId); if (!empty($value)) { $values[$key] = $value; } } return $values; } - - /** - * Get Merchant account ID - * - * @return string - */ - public function getMerchantAccountId() - { - return $this->getValue(self::KEY_MERCHANT_ACCOUNT_ID); - } } diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/AbstractTransaction.php b/app/code/Magento/Braintree/Gateway/Http/Client/AbstractTransaction.php index caeaaa7fe45a2..ef35152bf7e95 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/AbstractTransaction.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/AbstractTransaction.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Http\Client; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Payment\Gateway\Http\ClientException; use Magento\Payment\Gateway\Http\ClientInterface; use Magento\Payment\Gateway\Http\TransferInterface; @@ -29,22 +29,22 @@ abstract class AbstractTransaction implements ClientInterface protected $customLogger; /** - * @var BraintreeAdapter + * @var BraintreeAdapterFactory */ - protected $adapter; + protected $adapterFactory; /** * Constructor * * @param LoggerInterface $logger * @param Logger $customLogger - * @param BraintreeAdapter $transaction + * @param BraintreeAdapterFactory $adapterFactory */ - public function __construct(LoggerInterface $logger, Logger $customLogger, BraintreeAdapter $adapter) + public function __construct(LoggerInterface $logger, Logger $customLogger, BraintreeAdapterFactory $adapterFactory) { $this->logger = $logger; $this->customLogger = $customLogger; - $this->adapter = $adapter; + $this->adapterFactory = $adapterFactory; } /** diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php index 180344ab7263f..6d43929db7675 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php @@ -16,9 +16,9 @@ class TransactionRefund extends AbstractTransaction */ protected function process(array $data) { - return $this->adapter->refund( - $data['transaction_id'], - $data[PaymentDataBuilder::AMOUNT] - ); + $storeId = $data['store_id'] ?? null; + + return $this->adapterFactory->create($storeId) + ->refund($data['transaction_id'], $data[PaymentDataBuilder::AMOUNT]); } } diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSale.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSale.php index 81c79e522907d..0e4481a43bc89 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSale.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSale.php @@ -15,6 +15,10 @@ class TransactionSale extends AbstractTransaction */ protected function process(array $data) { - return $this->adapter->sale($data); + $storeId = $data['store_id'] ?? null; + // sending store id and other additional keys are restricted by Braintree API + unset($data['store_id']); + + return $this->adapterFactory->create($storeId)->sale($data); } } diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php index 0df5799b54b83..6760e724fd3a6 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php @@ -18,9 +18,9 @@ class TransactionSubmitForSettlement extends AbstractTransaction */ protected function process(array $data) { - return $this->adapter->submitForSettlement( - $data[CaptureDataBuilder::TRANSACTION_ID], - $data[PaymentDataBuilder::AMOUNT] - ); + $storeId = $data['store_id'] ?? null; + + return $this->adapterFactory->create($storeId) + ->submitForSettlement($data[CaptureDataBuilder::TRANSACTION_ID], $data[PaymentDataBuilder::AMOUNT]); } } diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php index a774065365b47..0a940839fc154 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php @@ -14,6 +14,8 @@ class TransactionVoid extends AbstractTransaction */ protected function process(array $data) { - return $this->adapter->void($data['transaction_id']); + $storeId = $data['store_id'] ?? null; + + return $this->adapterFactory->create($storeId)->void($data['transaction_id']); } } diff --git a/app/code/Magento/Braintree/Gateway/Request/AddressDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/AddressDataBuilder.php index 9ff65149894f8..f7d3aae823e56 100644 --- a/app/code/Magento/Braintree/Gateway/Request/AddressDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/AddressDataBuilder.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Payment\Gateway\Request\BuilderInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; /** * Class AddressDataBuilder diff --git a/app/code/Magento/Braintree/Gateway/Request/CaptureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/CaptureDataBuilder.php index c6588cfaca05f..6f3a262d7efb4 100644 --- a/app/code/Magento/Braintree/Gateway/Request/CaptureDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/CaptureDataBuilder.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Framework\Exception\LocalizedException; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; diff --git a/app/code/Magento/Braintree/Gateway/Request/CustomerDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/CustomerDataBuilder.php index 6b03f418c2545..6b3403bcd15c1 100644 --- a/app/code/Magento/Braintree/Gateway/Request/CustomerDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/CustomerDataBuilder.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Payment\Gateway\Request\BuilderInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; /** * Class CustomerDataBuilder diff --git a/app/code/Magento/Braintree/Gateway/Request/DescriptorDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/DescriptorDataBuilder.php index 3794058c2be8c..aac603bfb621a 100644 --- a/app/code/Magento/Braintree/Gateway/Request/DescriptorDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/DescriptorDataBuilder.php @@ -5,6 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Braintree\Gateway\Config\Config; @@ -24,21 +25,29 @@ class DescriptorDataBuilder implements BuilderInterface private $config; /** - * DescriptorDataBuilder constructor. + * @var SubjectReader + */ + private $subjectReader; + + /** * @param Config $config + * @param SubjectReader $subjectReader */ - public function __construct(Config $config) + public function __construct(Config $config, SubjectReader $subjectReader) { $this->config = $config; + $this->subjectReader = $subjectReader; } /** * @inheritdoc - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function build(array $buildSubject) { - $values = $this->config->getDynamicDescriptors(); + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + $values = $this->config->getDynamicDescriptors($order->getStoreId()); return !empty($values) ? [self::$descriptorKey => $values] : []; } } diff --git a/app/code/Magento/Braintree/Gateway/Request/KountPaymentDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/KountPaymentDataBuilder.php index 7eebbca1d4290..8538667778504 100644 --- a/app/code/Magento/Braintree/Gateway/Request/KountPaymentDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/KountPaymentDataBuilder.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Request\BuilderInterface; @@ -48,10 +48,12 @@ public function __construct(Config $config, SubjectReader $subjectReader) public function build(array $buildSubject) { $result = []; - if (!$this->config->hasFraudProtection()) { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + if (!$this->config->hasFraudProtection($order->getStoreId())) { return $result; } - $paymentDO = $this->subjectReader->readPayment($buildSubject); $payment = $paymentDO->getPayment(); $data = $payment->getAdditionalInformation(); diff --git a/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php new file mode 100644 index 0000000000000..6dc40e76322df --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Gateway\Request; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Request\BuilderInterface; + +/** + * Adds Merchant Account ID to the request if it was specified in the configuration. + */ +class MerchantAccountDataBuilder implements BuilderInterface +{ + /** + * The merchant account ID used to create a transaction. + * Currency is also determined by merchant account ID. + * If no merchant account ID is specified, Braintree will use your default merchant account. + */ + private static $merchantAccountId = 'merchantAccountId'; + + /** + * @var Config + */ + private $config; + + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * Constructor + * + * @param Config $config + * @param SubjectReader $subjectReader + */ + public function __construct(Config $config, SubjectReader $subjectReader) + { + $this->config = $config; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + $result = []; + $merchantAccountId = $this->config->getMerchantAccountId($order->getStoreId()); + if (!empty($merchantAccountId)) { + $result[self::$merchantAccountId] = $merchantAccountId; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Request/PayPal/DeviceDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PayPal/DeviceDataBuilder.php index cea0f8f1291bb..7d0d9dad0db06 100644 --- a/app/code/Magento/Braintree/Gateway/Request/PayPal/DeviceDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/PayPal/DeviceDataBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request\PayPal; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Request\BuilderInterface; diff --git a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php index 6f76c3415c31a..a035c84b4cafd 100644 --- a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request\PayPal; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Vault\Model\Ui\VaultConfigProvider; diff --git a/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php index a3341c3fc1873..fe75ce86cca2f 100644 --- a/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php @@ -6,8 +6,8 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Observer\DataAssignObserver; -use Magento\Braintree\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; @@ -36,9 +36,8 @@ class PaymentDataBuilder implements BuilderInterface const PAYMENT_METHOD_NONCE = 'paymentMethodNonce'; /** - * The merchant account ID used to create a transaction. - * Currency is also determined by merchant account ID. - * If no merchant account ID is specified, Braintree will use your default merchant account. + * @deprecated + * @see \Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder */ const MERCHANT_ACCOUNT_ID = 'merchantAccountId'; @@ -47,25 +46,18 @@ class PaymentDataBuilder implements BuilderInterface */ const ORDER_ID = 'orderId'; - /** - * @var Config - */ - private $config; - /** * @var SubjectReader */ private $subjectReader; /** - * Constructor - * * @param Config $config * @param SubjectReader $subjectReader + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct(Config $config, SubjectReader $subjectReader) { - $this->config = $config; $this->subjectReader = $subjectReader; } @@ -87,11 +79,6 @@ public function build(array $buildSubject) self::ORDER_ID => $order->getOrderIncrementId() ]; - $merchantAccountId = $this->config->getMerchantAccountId(); - if (!empty($merchantAccountId)) { - $result[self::MERCHANT_ACCOUNT_ID] = $merchantAccountId; - } - return $result; } } diff --git a/app/code/Magento/Braintree/Gateway/Request/RefundDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/RefundDataBuilder.php index 82de8e84cbfea..1c25646311160 100644 --- a/app/code/Magento/Braintree/Gateway/Request/RefundDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/RefundDataBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; use Magento\Sales\Api\Data\TransactionInterface; diff --git a/app/code/Magento/Braintree/Gateway/Request/StoreConfigBuilder.php b/app/code/Magento/Braintree/Gateway/Request/StoreConfigBuilder.php new file mode 100644 index 0000000000000..014df33690fa0 --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Request/StoreConfigBuilder.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Gateway\Request; + +use Magento\Payment\Gateway\Request\BuilderInterface; +use Magento\Braintree\Gateway\SubjectReader; + +/** + * This builder is used for correct store resolving and used only to retrieve correct store ID. + * The data from this build won't be send to Braintree Gateway. + */ +class StoreConfigBuilder implements BuilderInterface +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @param SubjectReader $subjectReader + */ + public function __construct(SubjectReader $subjectReader) + { + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject) + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + return [ + 'store_id' => $order->getStoreId() + ]; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Request/ThreeDSecureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/ThreeDSecureDataBuilder.php index c366a67701521..520aa58457753 100644 --- a/app/code/Magento/Braintree/Gateway/Request/ThreeDSecureDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/ThreeDSecureDataBuilder.php @@ -7,7 +7,7 @@ use Magento\Braintree\Gateway\Config\Config; use Magento\Payment\Gateway\Data\OrderAdapterInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; @@ -64,12 +64,15 @@ public function build(array $buildSubject) */ private function is3DSecureEnabled(OrderAdapterInterface $order, $amount) { - if (!$this->config->isVerify3DSecure() || $amount < $this->config->getThresholdAmount()) { + $storeId = $order->getStoreId(); + if (!$this->config->isVerify3DSecure($storeId) + || $amount < $this->config->getThresholdAmount($storeId) + ) { return false; } $billingAddress = $order->getBillingAddress(); - $specificCounties = $this->config->get3DSecureSpecificCountries(); + $specificCounties = $this->config->get3DSecureSpecificCountries($storeId); if (!empty($specificCounties) && !in_array($billingAddress->getCountryId(), $specificCounties)) { return false; } diff --git a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php index 7182f87e082d1..4280663178efb 100644 --- a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; diff --git a/app/code/Magento/Braintree/Gateway/Request/VoidDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VoidDataBuilder.php index 2328ea10f78c7..0bbda28cd344b 100644 --- a/app/code/Magento/Braintree/Gateway/Request/VoidDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/VoidDataBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Request; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Sales\Model\Order\Payment; diff --git a/app/code/Magento/Braintree/Gateway/Response/CancelDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/CancelDetailsHandler.php new file mode 100644 index 0000000000000..3d6ed025791bf --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Response/CancelDetailsHandler.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Gateway\Response; + +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; + +/** + * Handles response details for order cancellation request. + */ +class CancelDetailsHandler implements HandlerInterface +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @param SubjectReader $subjectReader + */ + public function __construct(SubjectReader $subjectReader) + { + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response) + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + /** @var Payment $orderPayment */ + $orderPayment = $paymentDO->getPayment(); + $orderPayment->setIsTransactionClosed(true); + $orderPayment->setShouldCloseParentTransaction(true); + } +} diff --git a/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php index 2715da5d3c419..32abeac4c8ffb 100644 --- a/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Response; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Helper\ContextHelper; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; @@ -85,7 +85,7 @@ public function handle(array $handlingSubject, array $response) private function getCreditCardType($type) { $replaced = str_replace(' ', '-', strtolower($type)); - $mapper = $this->config->getCctypesMapper(); + $mapper = $this->config->getCcTypesMapper(); return $mapper[$replaced]; } diff --git a/app/code/Magento/Braintree/Gateway/Response/PayPal/VaultDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/PayPal/VaultDetailsHandler.php index 766b385851ab8..7d38c2bf7d2ed 100644 --- a/app/code/Magento/Braintree/Gateway/Response/PayPal/VaultDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/PayPal/VaultDetailsHandler.php @@ -6,14 +6,14 @@ namespace Magento\Braintree\Gateway\Response\PayPal; use Braintree\Transaction; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Framework\Intl\DateTimeFactory; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Payment\Model\InfoInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; -use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; /** * Vault Details Handler @@ -21,7 +21,7 @@ class VaultDetailsHandler implements HandlerInterface { /** - * @var PaymentTokenInterfaceFactory + * @var PaymentTokenFactoryInterface */ private $paymentTokenFactory; @@ -41,15 +41,13 @@ class VaultDetailsHandler implements HandlerInterface private $dateTimeFactory; /** - * Constructor - * - * @param PaymentTokenInterfaceFactory $paymentTokenFactory + * @param PaymentTokenFactoryInterface $paymentTokenFactory * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory * @param SubjectReader $subjectReader * @param DateTimeFactory $dateTimeFactory */ public function __construct( - PaymentTokenInterfaceFactory $paymentTokenFactory, + PaymentTokenFactoryInterface $paymentTokenFactory, OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, SubjectReader $subjectReader, DateTimeFactory $dateTimeFactory @@ -92,7 +90,7 @@ private function getVaultPaymentToken(Transaction $transaction) } /** @var PaymentTokenInterface $paymentToken */ - $paymentToken = $this->paymentTokenFactory->create(); + $paymentToken = $this->paymentTokenFactory->create(PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT); $paymentToken->setGatewayToken($token); $paymentToken->setExpiresAt($this->getExpirationDate()); $details = json_encode([ diff --git a/app/code/Magento/Braintree/Gateway/Response/PayPalDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/PayPalDetailsHandler.php index 83d7e44a6b612..97bb312af4bd4 100644 --- a/app/code/Magento/Braintree/Gateway/Response/PayPalDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/PayPalDetailsHandler.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Response; use Magento\Payment\Gateway\Response\HandlerInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Sales\Api\Data\OrderPaymentInterface; /** diff --git a/app/code/Magento/Braintree/Gateway/Response/PaymentDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/PaymentDetailsHandler.php index 3941640aaeeb1..a95ea76c2e633 100644 --- a/app/code/Magento/Braintree/Gateway/Response/PaymentDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/PaymentDetailsHandler.php @@ -5,10 +5,8 @@ */ namespace Magento\Braintree\Gateway\Response; -use Braintree\Transaction; use Magento\Braintree\Observer\DataAssignObserver; -use Magento\Payment\Gateway\Helper\ContextHelper; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; diff --git a/app/code/Magento/Braintree/Gateway/Response/RiskDataHandler.php b/app/code/Magento/Braintree/Gateway/Response/RiskDataHandler.php index 95660e10b394c..d4976ff18e0ee 100644 --- a/app/code/Magento/Braintree/Gateway/Response/RiskDataHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/RiskDataHandler.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Gateway\Response; use Magento\Payment\Gateway\Helper\ContextHelper; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; /** diff --git a/app/code/Magento/Braintree/Gateway/Response/ThreeDSecureDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/ThreeDSecureDetailsHandler.php index 93f37cb561feb..8d61660f03ce5 100644 --- a/app/code/Magento/Braintree/Gateway/Response/ThreeDSecureDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/ThreeDSecureDetailsHandler.php @@ -7,7 +7,7 @@ use Braintree\Transaction; use Magento\Payment\Gateway\Helper\ContextHelper; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; diff --git a/app/code/Magento/Braintree/Gateway/Response/TransactionIdHandler.php b/app/code/Magento/Braintree/Gateway/Response/TransactionIdHandler.php index 7dd79143736e5..18888bdcf3d4a 100644 --- a/app/code/Magento/Braintree/Gateway/Response/TransactionIdHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/TransactionIdHandler.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Gateway\Response; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Sales\Model\Order\Payment; diff --git a/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php index a8332b9409f78..8880f9c1b1a3e 100644 --- a/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php @@ -7,13 +7,15 @@ use Braintree\Transaction; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Payment\Gateway\Response\HandlerInterface; use Magento\Payment\Model\InfoInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; -use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; /** * Vault Details Handler @@ -22,7 +24,7 @@ class VaultDetailsHandler implements HandlerInterface { /** - * @var PaymentTokenInterfaceFactory + * @var PaymentTokenFactoryInterface */ protected $paymentTokenFactory; @@ -42,33 +44,32 @@ class VaultDetailsHandler implements HandlerInterface protected $config; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var Json */ private $serializer; /** * VaultDetailsHandler constructor. * - * @param PaymentTokenInterfaceFactory $paymentTokenFactory + * @param PaymentTokenFactoryInterface $paymentTokenFactory * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory * @param Config $config * @param SubjectReader $subjectReader - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + * @param Json|null $serializer * @throws \RuntimeException */ public function __construct( - PaymentTokenInterfaceFactory $paymentTokenFactory, + PaymentTokenFactoryInterface $paymentTokenFactory, OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, Config $config, SubjectReader $subjectReader, - \Magento\Framework\Serialize\Serializer\Json $serializer = null + Json $serializer = null ) { $this->paymentTokenFactory = $paymentTokenFactory; $this->paymentExtensionFactory = $paymentExtensionFactory; $this->config = $config; $this->subjectReader = $subjectReader; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); } /** @@ -103,7 +104,7 @@ protected function getVaultPaymentToken(Transaction $transaction) } /** @var PaymentTokenInterface $paymentToken */ - $paymentToken = $this->paymentTokenFactory->create(); + $paymentToken = $this->paymentTokenFactory->create(PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD); $paymentToken->setGatewayToken($token); $paymentToken->setExpiresAt($this->getExpirationDate($transaction)); @@ -156,7 +157,7 @@ private function convertDetailsToJSON($details) private function getCreditCardType($type) { $replaced = str_replace(' ', '-', strtolower($type)); - $mapper = $this->config->getCctypesMapper(); + $mapper = $this->config->getCcTypesMapper(); return $mapper[$replaced]; } diff --git a/app/code/Magento/Braintree/Gateway/Helper/SubjectReader.php b/app/code/Magento/Braintree/Gateway/SubjectReader.php similarity index 85% rename from app/code/Magento/Braintree/Gateway/Helper/SubjectReader.php rename to app/code/Magento/Braintree/Gateway/SubjectReader.php index e98597655190f..7cf00233e7f8f 100644 --- a/app/code/Magento/Braintree/Gateway/Helper/SubjectReader.php +++ b/app/code/Magento/Braintree/Gateway/SubjectReader.php @@ -3,13 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Braintree\Gateway\Helper; +namespace Magento\Braintree\Gateway; use Braintree\Transaction; -use Magento\Quote\Model\Quote; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Helper; use Magento\Vault\Api\Data\PaymentTokenInterface; -use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; /** * Class SubjectReader @@ -44,19 +43,20 @@ public function readPayment(array $subject) } /** - * Reads transaction from subject + * Reads transaction from the subject. * * @param array $subject - * @return \Braintree\Transaction + * @return Transaction + * @throws \InvalidArgumentException if the subject doesn't contain transaction details. */ public function readTransaction(array $subject) { if (!isset($subject['object']) || !is_object($subject['object'])) { - throw new \InvalidArgumentException('Response object does not exist'); + throw new \InvalidArgumentException('Response object does not exist.'); } if (!isset($subject['object']->transaction) - && !$subject['object']->transaction instanceof Transaction + || !$subject['object']->transaction instanceof Transaction ) { throw new \InvalidArgumentException('The object is not a class \Braintree\Transaction.'); } @@ -119,4 +119,15 @@ public function readPayPal(Transaction $transaction) return $transaction->paypal; } + + /** + * Reads store's ID, otherwise returns null. + * + * @param array $subject + * @return int|null + */ + public function readStoreId(array $subject) + { + return $subject['store_id'] ?? null; + } } diff --git a/app/code/Magento/Braintree/Gateway/Validator/CancelResponseValidator.php b/app/code/Magento/Braintree/Gateway/Validator/CancelResponseValidator.php new file mode 100644 index 0000000000000..5e31547e9503c --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Validator/CancelResponseValidator.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Gateway\Validator; + +use Braintree\Error\ErrorCollection; +use Braintree\Error\Validation; +use Magento\Payment\Gateway\Validator\AbstractValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; +use Magento\Braintree\Gateway\SubjectReader; + +/** + * Decorates the general response validator to handle specific cases. + * + * This validator decorates the general response validator to handle specific cases like + * an expired or already voided on Braintree side authorization transaction. + */ +class CancelResponseValidator extends AbstractValidator +{ + /** + * @var int + */ + private static $acceptableTransactionCode = 91504; + + /** + * @var GeneralResponseValidator + */ + private $generalResponseValidator; + + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @param ResultInterfaceFactory $resultFactory + * @param GeneralResponseValidator $generalResponseValidator + * @param SubjectReader $subjectReader + */ + public function __construct( + ResultInterfaceFactory $resultFactory, + GeneralResponseValidator $generalResponseValidator, + SubjectReader $subjectReader + ) { + parent::__construct($resultFactory); + $this->generalResponseValidator = $generalResponseValidator; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function validate(array $validationSubject): ResultInterface + { + $result = $this->generalResponseValidator->validate($validationSubject); + if (!$result->isValid()) { + $response = $this->subjectReader->readResponseObject($validationSubject); + if ($this->isErrorAcceptable($response->errors)) { + $result = $this->createResult(true, [__('Transaction is cancelled offline.')]); + } + } + + return $result; + } + + /** + * Checks if error collection has an acceptable error code. + * + * @param ErrorCollection $errorCollection + * @return bool + */ + private function isErrorAcceptable(ErrorCollection $errorCollection): bool + { + $errors = $errorCollection->deepAll(); + // there is should be only one acceptable error + if (count($errors) > 1) { + return false; + } + + /** @var Validation $error */ + $error = array_pop($errors); + + return (int)$error->code === self::$acceptableTransactionCode; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php new file mode 100644 index 0000000000000..167fcb1569cbf --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Gateway\Validator; + +use Braintree\Error\ErrorCollection; +use Braintree\Error\Validation; +use Braintree\Result\Error; +use Braintree\Result\Successful; + +/** + * Processes errors codes from Braintree response. + */ +class ErrorCodeProvider +{ + /** + * Retrieves list of error codes from Braintree response. + * + * @param Successful|Error $response + * @return array + */ + public function getErrorCodes($response): array + { + $result = []; + if (!$response instanceof Error) { + return $result; + } + + /** @var ErrorCollection $collection */ + $collection = $response->errors; + + /** @var Validation $error */ + foreach ($collection->deepAll() as $error) { + $result[] = $error->code; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php index a70bc2d02e0e5..6aac588c38374 100644 --- a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php +++ b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php @@ -8,7 +8,7 @@ use Braintree\Result\Error; use Braintree\Result\Successful; use Magento\Payment\Gateway\Validator\AbstractValidator; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; class GeneralResponseValidator extends AbstractValidator @@ -18,16 +18,26 @@ class GeneralResponseValidator extends AbstractValidator */ protected $subjectReader; + /** + * @var ErrorCodeProvider + */ + private $errorCodeProvider; + /** * Constructor * * @param ResultInterfaceFactory $resultFactory * @param SubjectReader $subjectReader + * @param ErrorCodeProvider $errorCodeProvider */ - public function __construct(ResultInterfaceFactory $resultFactory, SubjectReader $subjectReader) - { + public function __construct( + ResultInterfaceFactory $resultFactory, + SubjectReader $subjectReader, + ErrorCodeProvider $errorCodeProvider + ) { parent::__construct($resultFactory); $this->subjectReader = $subjectReader; + $this->errorCodeProvider = $errorCodeProvider; } /** @@ -49,8 +59,9 @@ public function validate(array $validationSubject) $errorMessages = array_merge($errorMessages, $validationResult[1]); } } + $errorCodes = $this->errorCodeProvider->getErrorCodes($response); - return $this->createResult($isValid, $errorMessages); + return $this->createResult($isValid, $errorMessages, $errorCodes); } /** @@ -62,7 +73,7 @@ protected function getResponseValidators() function ($response) { return [ property_exists($response, 'success') && $response->success === true, - [__('Braintree error response.')] + [$response->message ?? __('Braintree error response.')] ]; } ]; diff --git a/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php index 71f3828ebe9d4..fd1fe81b5eba8 100644 --- a/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php +++ b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapter.php @@ -15,29 +15,40 @@ /** * Class BraintreeAdapter + * Use \Magento\Braintree\Model\Adapter\BraintreeAdapterFactory to create new instance of adapter. * @codeCoverageIgnore */ class BraintreeAdapter { - /** * @var Config */ private $config; /** - * @param Config $config + * @param string $merchantId + * @param string $publicKey + * @param string $privateKey + * @param string $environment */ - public function __construct(Config $config) + public function __construct($merchantId, $publicKey, $privateKey, $environment) { - $this->config = $config; - $this->initCredentials(); + $this->merchantId($merchantId); + $this->publicKey($publicKey); + $this->privateKey($privateKey); + + if ($environment === Environment::ENVIRONMENT_PRODUCTION) { + $this->environment(Environment::ENVIRONMENT_PRODUCTION); + } else { + $this->environment(Environment::ENVIRONMENT_SANDBOX); + } } /** * Initializes credentials. * * @return void + * @deprecated is not used anymore */ protected function initCredentials() { diff --git a/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapterFactory.php b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapterFactory.php new file mode 100644 index 0000000000000..2c3f137eb1686 --- /dev/null +++ b/app/code/Magento/Braintree/Model/Adapter/BraintreeAdapterFactory.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Model\Adapter; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Framework\ObjectManagerInterface; + +/** + * This factory is preferable to use for Braintree adapter instance creation. + */ +class BraintreeAdapterFactory +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Config + */ + private $config; + + /** + * @param ObjectManagerInterface $objectManager + * @param Config $config + */ + public function __construct(ObjectManagerInterface $objectManager, Config $config) + { + $this->config = $config; + $this->objectManager = $objectManager; + } + + /** + * Creates instance of Braintree Adapter. + * + * @param int|null $storeId if null is provided as an argument, then current scope will be resolved + * by \Magento\Framework\App\Config\ScopeCodeResolver (useful for most cases) but for adminhtml area the store + * should be provided as the argument for correct config settings loading. + * @return BraintreeAdapter + */ + public function create($storeId = null) + { + return $this->objectManager->create( + BraintreeAdapter::class, + [ + 'merchantId' => $this->config->getMerchantId($storeId), + 'publicKey' => $this->config->getValue(Config::KEY_PUBLIC_KEY, $storeId), + 'privateKey' => $this->config->getValue(Config::KEY_PRIVATE_KEY, $storeId), + 'environment' => $this->config->getEnvironment($storeId), + ] + ); + } +} diff --git a/app/code/Magento/Braintree/Model/Adminhtml/Source/PaymentAction.php b/app/code/Magento/Braintree/Model/Adminhtml/Source/PaymentAction.php index e0d684a7aa976..595d8b4792a62 100644 --- a/app/code/Magento/Braintree/Model/Adminhtml/Source/PaymentAction.php +++ b/app/code/Magento/Braintree/Model/Adminhtml/Source/PaymentAction.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Model\Adminhtml\Source; use Magento\Framework\Option\ArrayInterface; -use Magento\Payment\Model\Method\AbstractMethod; +use Magento\Payment\Model\MethodInterface; /** * Class PaymentAction @@ -22,11 +22,11 @@ public function toOptionArray() { return [ [ - 'value' => AbstractMethod::ACTION_AUTHORIZE, + 'value' => MethodInterface::ACTION_AUTHORIZE, 'label' => __('Authorize'), ], [ - 'value' => AbstractMethod::ACTION_AUTHORIZE_CAPTURE, + 'value' => MethodInterface::ACTION_AUTHORIZE_CAPTURE, 'label' => __('Authorize and Capture'), ] ]; diff --git a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php index f68b7eca047f5..2a9923a333cef 100644 --- a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php +++ b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php @@ -66,6 +66,13 @@ public function __construct( public function beforeSave() { $value = $this->getValue(); + if (!is_array($value)) { + try { + $value = $this->serializer->unserialize($value); + } catch (\InvalidArgumentException $e) { + $value = []; + } + } $result = []; foreach ($value as $data) { if (empty($data['country_id']) || empty($data['cc_types'])) { diff --git a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php index 1d5057d83d6cf..f9fae8a469b1d 100644 --- a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php +++ b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php @@ -24,7 +24,7 @@ class AvsEmsCodeMapper implements PaymentVerificationInterface * * @var string */ - private static $unavailableCode = 'U'; + private static $unavailableCode = ''; /** * List of mapping AVS codes diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php index b833798eabf90..6c4332ef22a4c 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Model\Paypal\Helper; use Magento\Quote\Model\Quote; @@ -71,7 +72,9 @@ public function __construct( public function execute(Quote $quote, array $agreement) { if (!$this->agreementsValidator->isValid($agreement)) { - throw new LocalizedException(__('Please agree to all the terms and conditions before placing the order.')); + throw new LocalizedException(__( + "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." + )); } if ($this->getCheckoutMethod($quote) === Onepage::METHOD_GUEST) { diff --git a/app/code/Magento/Braintree/Model/Report/TransactionsCollection.php b/app/code/Magento/Braintree/Model/Report/TransactionsCollection.php index edac6e3533849..a237b64bf58ee 100644 --- a/app/code/Magento/Braintree/Model/Report/TransactionsCollection.php +++ b/app/code/Magento/Braintree/Model/Report/TransactionsCollection.php @@ -5,7 +5,8 @@ */ namespace Magento\Braintree\Model\Report; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Braintree\Model\Report\Row\TransactionMap; use Magento\Framework\Api\Search\SearchResultInterface; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Data\Collection; @@ -26,7 +27,7 @@ class TransactionsCollection extends Collection implements SearchResultInterface * * @var string */ - protected $_itemObjectClass = \Magento\Braintree\Model\Report\Row\TransactionMap::class; + protected $_itemObjectClass = TransactionMap::class; /** * @var array @@ -39,9 +40,9 @@ class TransactionsCollection extends Collection implements SearchResultInterface private $filterMapper; /** - * @var BraintreeAdapter + * @var BraintreeAdapterFactory */ - private $braintreeAdapter; + private $braintreeAdapterFactory; /** * @var \Braintree\ResourceCollection | null @@ -50,17 +51,17 @@ class TransactionsCollection extends Collection implements SearchResultInterface /** * @param EntityFactoryInterface $entityFactory - * @param BraintreeAdapter $braintreeAdapter + * @param BraintreeAdapterFactory $braintreeAdapterFactory * @param FilterMapper $filterMapper */ public function __construct( EntityFactoryInterface $entityFactory, - BraintreeAdapter $braintreeAdapter, + BraintreeAdapterFactory $braintreeAdapterFactory, FilterMapper $filterMapper ) { parent::__construct($entityFactory); $this->filterMapper = $filterMapper; - $this->braintreeAdapter = $braintreeAdapter; + $this->braintreeAdapterFactory = $braintreeAdapterFactory; } /** @@ -110,7 +111,8 @@ protected function fetchIdsCollection() // Fetch all transaction IDs in order to filter if (empty($this->collection)) { $filters = $this->getFilters(); - $this->collection = $this->braintreeAdapter->search($filters); + $this->collection = $this->braintreeAdapterFactory->create() + ->search($filters); } return $this->collection; diff --git a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php index c420195446270..928769498a035 100644 --- a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php @@ -5,10 +5,11 @@ */ namespace Magento\Braintree\Model\Ui; +use Magento\Braintree\Gateway\Config\Config; use Magento\Braintree\Gateway\Request\PaymentDataBuilder; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Checkout\Model\ConfigProviderInterface; -use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Framework\Session\SessionManagerInterface; /** * Class ConfigProvider @@ -25,27 +26,35 @@ class ConfigProvider implements ConfigProviderInterface private $config; /** - * @var BraintreeAdapter + * @var BraintreeAdapterFactory */ - private $adapter; + private $adapterFactory; /** * @var string */ private $clientToken = ''; + /** + * @var SessionManagerInterface + */ + private $session; + /** * Constructor * * @param Config $config - * @param BraintreeAdapter $adapter + * @param BraintreeAdapterFactory $adapterFactory + * @param SessionManagerInterface $session */ public function __construct( Config $config, - BraintreeAdapter $adapter + BraintreeAdapterFactory $adapterFactory, + SessionManagerInterface $session ) { $this->config = $config; - $this->adapter = $adapter; + $this->adapterFactory = $adapterFactory; + $this->session = $session; } /** @@ -55,28 +64,29 @@ public function __construct( */ public function getConfig() { + $storeId = $this->session->getStoreId(); return [ 'payment' => [ self::CODE => [ - 'isActive' => $this->config->isActive(), + 'isActive' => $this->config->isActive($storeId), 'clientToken' => $this->getClientToken(), - 'ccTypesMapper' => $this->config->getCctypesMapper(), + 'ccTypesMapper' => $this->config->getCcTypesMapper(), 'sdkUrl' => $this->config->getSdkUrl(), - 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig(), - 'availableCardTypes' => $this->config->getAvailableCardTypes(), - 'useCvv' => $this->config->isCvvEnabled(), - 'environment' => $this->config->getEnvironment(), - 'kountMerchantId' => $this->config->getKountMerchantId(), - 'hasFraudProtection' => $this->config->hasFraudProtection(), - 'merchantId' => $this->config->getMerchantId(), - 'ccVaultCode' => self::CC_VAULT_CODE + 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig($storeId), + 'availableCardTypes' => $this->config->getAvailableCardTypes($storeId), + 'useCvv' => $this->config->isCvvEnabled($storeId), + 'environment' => $this->config->getEnvironment($storeId), + 'kountMerchantId' => $this->config->getKountMerchantId($storeId), + 'hasFraudProtection' => $this->config->hasFraudProtection($storeId), + 'merchantId' => $this->config->getMerchantId($storeId), + 'ccVaultCode' => self::CC_VAULT_CODE, ], Config::CODE_3DSECURE => [ - 'enabled' => $this->config->isVerify3DSecure(), - 'thresholdAmount' => $this->config->getThresholdAmount(), - 'specificCountries' => $this->config->get3DSecureSpecificCountries() + 'enabled' => $this->config->isVerify3DSecure($storeId), + 'thresholdAmount' => $this->config->getThresholdAmount($storeId), + 'specificCountries' => $this->config->get3DSecureSpecificCountries($storeId), ], - ] + ], ]; } @@ -89,12 +99,14 @@ public function getClientToken() if (empty($this->clientToken)) { $params = []; - $merchantAccountId = $this->config->getMerchantAccountId(); + $storeId = $this->session->getStoreId(); + $merchantAccountId = $this->config->getMerchantAccountId($storeId); if (!empty($merchantAccountId)) { $params[PaymentDataBuilder::MERCHANT_ACCOUNT_ID] = $merchantAccountId; } - $this->clientToken = $this->adapter->generate($params); + $this->clientToken = $this->adapterFactory->create($storeId) + ->generate($params); } return $this->clientToken; diff --git a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php index e06b913db8ef4..e6c5ee22c62b4 100644 --- a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php @@ -47,6 +47,8 @@ public function __construct(Config $config, ResolverInterface $resolver) */ public function getConfig() { + $requireBillingAddressAll = \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL; + return [ 'payment' => [ self::PAYPAL_CODE => [ @@ -60,6 +62,8 @@ public function getConfig() 'vaultCode' => self::PAYPAL_VAULT_CODE, 'skipOrderReview' => $this->config->isSkipOrderReview(), 'paymentIcon' => $this->config->getPayPalIcon(), + 'isRequiredBillingAddress' => + (int)$this->config->isRequiredBillingAddress() === $requireBillingAddressAll ] ] ]; diff --git a/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php b/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php new file mode 100644 index 0000000000000..d08bf62da8e4f --- /dev/null +++ b/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Braintree\Setup\Patch\Data; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; + +/** + * Convert data from php native serialized data to JSON. + */ +class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var \Magento\Framework\DB\FieldDataConverterFactory + */ + private $fieldDataConverterFactory; + + /** + * @var \Magento\Framework\DB\Select\QueryModifierFactory + */ + private $queryModifierFactory; + + /** + * ConvertSerializedDataToJson constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + * @param \Magento\Framework\DB\FieldDataConverterFactory $fieldDataConverterFactory + * @param \Magento\Framework\DB\Select\QueryModifierFactory $queryModifierFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + \Magento\Framework\DB\FieldDataConverterFactory $fieldDataConverterFactory, + \Magento\Framework\DB\Select\QueryModifierFactory $queryModifierFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->fieldDataConverterFactory = $fieldDataConverterFactory; + $this->queryModifierFactory = $queryModifierFactory; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->convertSerializedDataToJson(); + } + + /** + * Upgrade data to version 2.0.1, converts row data in the core_config_data table that uses the path + * payment/braintree/countrycreditcard from serialized to JSON + * + * @return void + */ + private function convertSerializedDataToJson() + { + $fieldDataConverter = $this->fieldDataConverterFactory->create( + \Magento\Framework\DB\DataConverter\SerializedToJson::class + ); + + $queryModifier = $this->queryModifierFactory->create( + 'in', + [ + 'values' => [ + 'path' => ['payment/braintree/countrycreditcard'] + ] + ] + ); + + $fieldDataConverter->convert( + $this->moduleDataSetup->getConnection(), + $this->moduleDataSetup->getTable('core_config_data'), + 'config_id', + 'value', + $queryModifier + ); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public static function getVersion() + { + return '2.0.1'; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Braintree/Setup/UpgradeData.php b/app/code/Magento/Braintree/Setup/UpgradeData.php deleted file mode 100644 index a7b39f12273e2..0000000000000 --- a/app/code/Magento/Braintree/Setup/UpgradeData.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Braintree\Setup; - -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\Setup\UpgradeDataInterface; - -class UpgradeData implements UpgradeDataInterface -{ - /** - * @var \Magento\Framework\DB\FieldDataConverterFactory - */ - private $fieldDataConverterFactory; - - /** - * @var \Magento\Framework\DB\Select\QueryModifierFactory - */ - private $queryModifierFactory; - - /** - * UpgradeData constructor. - * - * @param \Magento\Framework\DB\FieldDataConverterFactory $fieldDataConverterFactory - * @param \Magento\Framework\DB\Select\QueryModifierFactory $queryModifierFactory - */ - public function __construct( - \Magento\Framework\DB\FieldDataConverterFactory $fieldDataConverterFactory, - \Magento\Framework\DB\Select\QueryModifierFactory $queryModifierFactory - ) { - $this->fieldDataConverterFactory = $fieldDataConverterFactory; - $this->queryModifierFactory = $queryModifierFactory; - } - - /** - * Upgrades data for Braintree module - * - * @param ModuleDataSetupInterface $setup - * @param ModuleContextInterface $context - * @return void - */ - public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) - { - if (version_compare($context->getVersion(), '2.0.1', '<')) { - $this->convertSerializedDataToJson($setup); - } - } - - /** - * Upgrade data to version 2.0.1, converts row data in the core_config_data table that uses the path - * payment/braintree/countrycreditcard from serialized to JSON - * - * @param ModuleDataSetupInterface $setup - * @return void - */ - private function convertSerializedDataToJson(ModuleDataSetupInterface $setup) - { - $fieldDataConverter = $this->fieldDataConverterFactory->create( - \Magento\Framework\DB\DataConverter\SerializedToJson::class - ); - - $queryModifier = $this->queryModifierFactory->create( - 'in', - [ - 'values' => [ - 'path' => ['payment/braintree/countrycreditcard'] - ] - ] - ); - - $fieldDataConverter->convert( - $setup->getConnection(), - $setup->getTable('core_config_data'), - 'config_id', - 'value', - $queryModifier - ); - } -} diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml new file mode 100644 index 0000000000000..e86d5403e11eb --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml @@ -0,0 +1,51 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="GoToUserRoles"> + <click selector="#menu-magento-backend-system" stepKey="clickOnSystemIcon"/> + <waitForPageLoad stepKey="waitForSystemsPageToOpen"/> + <click selector="//span[contains(text(), 'User Roles')]" stepKey="clickToSelectUserRoles"/> + <waitForPageLoad stepKey="waitForUserRolesPageToOpen"/> + </actionGroup> + + <!--Create new role--> + <actionGroup name="AdminCreateRole"> + <arguments> + <argument name="role" type="string" defaultValue=""/> + <argument name="resource" type="string" defaultValue="All"/> + <argument name="scope" type="string" defaultValue="Custom"/> + <argument name="websites" type="string" defaultValue="Main Website"/> + </arguments> + <click selector="{{AdminCreateRoleSection.create}}" stepKey="clickToAddNewRole"/> + <fillField selector="{{AdminCreateRoleSection.name}}" userInput="{{role.name}}" stepKey="setRoleName"/> + <fillField stepKey="setPassword" selector="{{AdminCreateRoleSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click selector="{{AdminCreateRoleSection.roleResources}}" stepKey="clickToOpenRoleResources"/> + <waitForPageLoad stepKey="waitForRoleResourcePage" time="5"/> + <click stepKey="checkSales" selector="//a[text()='Sales']"/> + <click selector="{{AdminCreateRoleSection.save}}" stepKey="clickToSaveRole"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see userInput="You saved the role." stepKey="seeSuccessMessage" /> + </actionGroup> + + + <!--Delete role--> + <actionGroup name="AdminDeleteRoleActionGroup"> + <arguments> + <argument name="role" defaultValue=""/> + </arguments> + <click stepKey="clickOnRole" selector="{{AdminDeleteRoleSection.theRole}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteRoleSection.current_pass}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click stepKey="clickToDeleteRole" selector="{{AdminDeleteRoleSection.delete}}"/> + <waitForAjaxLoad stepKey="waitForDeleteConfirmationPopup" time="5"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteRoleSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see stepKey="seeSuccessMessage" userInput="You deleted the role."/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml new file mode 100644 index 0000000000000..23c322083773c --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml @@ -0,0 +1,56 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Go to all users--> + <actionGroup name="GoToAllUsers"> + <click selector="{{AdminCreateUserSection.system}}" stepKey="clickOnSystemIcon"/> + <waitForPageLoad stepKey="waitForSystemsPageToOpen"/> + <click selector="{{AdminCreateUserSection.allUsers}}" stepKey="clickToSelectUserRoles"/> + <waitForPageLoad stepKey="waitForUserRolesPageToOpen"/> + </actionGroup> + + <!--Create new user with specified role--> + <actionGroup name="AdminCreateUserAction"> + <click selector="{{AdminCreateUserSection.create}}" stepKey="clickToCreateNewUser"/> + <waitForPageLoad stepKey="waitForNewUserPageLoad" time="10"/> + <fillField selector="{{AdminCreateUserSection.usernameTextField}}" userInput="{{NewAdmin.username}}" stepKey="enterUserName" /> + <fillField selector="{{AdminCreateUserSection.firstNameTextField}}" userInput="{{NewAdmin.firstName}}" stepKey="enterFirstName" /> + <fillField selector="{{AdminCreateUserSection.lastNameTextField}}" userInput="{{NewAdmin.lastName}}" stepKey="enterLastName" /> + <fillField selector="{{AdminCreateUserSection.emailTextField}}" userInput="{{NewAdmin.email}}" stepKey="enterEmail" /> + <fillField selector="{{AdminCreateUserSection.passwordTextField}}" userInput="{{NewAdmin.password}}" stepKey="enterPassword" /> + <fillField selector="{{AdminCreateUserSection.pwConfirmationTextField}}" userInput="{{NewAdmin.password}}" stepKey="confirmPassword" /> + <fillField selector="{{AdminCreateUserSection.currentPasswordField}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterCurrentPassword" /> + <scrollToTopOfPage stepKey="scrollToTopOfPage" /> + <click selector="{{AdminCreateUserSection.userRoleTab}}" stepKey="clickUserRole" /> + <waitForAjaxLoad stepKey="waitForRoles" time="5"/> + <fillField selector="{{AdminCreateRoleSection.roleNameFilterTextField}}" userInput="{{role.name}}" stepKey="filterRole" /> + <click selector="{{AdminCreateRoleSection.searchButton}}" stepKey="clickSearch" /> + <waitForPageLoad stepKey="waitForSearch" time="10"/> + <click selector="{{AdminCreateRoleSection.searchResultFirstRow}}" stepKey="selectRole" /> + <click selector="{{AdminCreateUserSection.saveButton}}" stepKey="clickSaveUser" /> + <waitForPageLoad stepKey="waitForSaveUser" time="10"/> + <see userInput="You saved the user." stepKey="seeSuccessMessage" /> + </actionGroup> + + + <!--Delete User--> + <actionGroup name="AdminDeleteUserActionGroup"> + + <click stepKey="clickOnUser" selector="{{AdminDeleteUserSection.theUser}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeletePopupOpen" time="5"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteUserSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see userInput="You deleted the user." stepKey="seeSuccessMessage" /> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml new file mode 100644 index 0000000000000..27e2039fe526e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml @@ -0,0 +1,49 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="ConfigureBraintree"> + <!-- GoTo ConfigureBraintree fields --> + <click stepKey="clickOnSTORES" selector="{{AdminMenuSection.stores}}"/> + <waitForPageLoad stepKey="waitForConfiguration" time="2"/> + <click stepKey="clickOnConfigurations" selector="{{StoresSubmenuSection.configuration}}" /> + <waitForPageLoad stepKey="waitForSales" time="2"/> + <click stepKey="clickOnSales" selector="{{ConfigurationListSection.sales}}" /> + <waitForPageLoad stepKey="waitForPaymentMethods" time="2"/> + <click stepKey="clickOnPaymentMethods" selector="{{ConfigurationListSection.salesPaymentMethods}}" /> + <waitForPageLoad stepKey="waitForConfigureButton" time="2"/> + <click stepKey="clickOnConfigureButtonForBraintree" selector="{{ConfigurationPaymentSection.configureButton}}" /> + <waitForPageLoad stepKey="BraintreeSettings" time="2"/> + + <!-- Fill Braintree fields --> + <fillField stepKey="fillTitleForBraintreeSettings" selector="{{BraintreeConfiguraionSection.titleForBraintreeSettings}}" userInput="{{BraintreeConfigurationData.title}}"/> + <click stepKey="openEnvironmentSelect" selector="{{BraintreeConfiguraionSection.environment}}"/> + <click stepKey="chooseEnvironment" selector="{{BraintreeConfiguraionSection.sandbox}}"/> + <click stepKey="openPaymentActionSelect" selector="{{BraintreeConfiguraionSection.paymentActionSelect}}"/> + <click stepKey="choosePaymentAction" selector="{{BraintreeConfiguraionSection.paymentAction}}"/> + <fillField stepKey="fillMerchantID" selector="{{BraintreeConfiguraionSection.merchantID}}" userInput="{{BraintreeConfigurationData.merchantID}}"/> + <fillField stepKey="fillPublicKey" selector="{{BraintreeConfiguraionSection.publicKey}}" userInput="{{BraintreeConfigurationData.publicKey}}"/> + <fillField stepKey="fillPrivateKey" selector="{{BraintreeConfiguraionSection.privateKey}}" userInput="{{BraintreeConfigurationData.privateKey}}"/> + <click stepKey="expandEnableThisSolution" selector="{{BraintreeConfiguraionSection.enableThisSolution}}"/> + <click stepKey="chooseYesForEnableThisSolution" selector="{{BraintreeConfiguraionSection.yesForEnable}}"/> + <click stepKey="expandEnablePayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.payPalThroughBraintree}}"/> + <click stepKey="chooseYesForEnablePayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.yesForPayPalThroughBraintree}}"/> + <click stepKey="expandAdvancedBraintreeSettings" selector="{{BraintreeConfiguraionSection.advancedBraintreeSettings}}"/> + <fillField stepKey="fillMerchantAccountID" selector="{{BraintreeConfiguraionSection.merchantAccountID}}" userInput="{{BraintreeConfigurationData.merchantAccountID}}"/> + <click stepKey="expandCVVVerification" selector="{{BraintreeConfiguraionSection.CVVVerification}}"/> + <click stepKey="chooseYes" selector="{{BraintreeConfiguraionSection.yesForCVV}}"/> + <click stepKey="expandPayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.payPalThroughBraintreeSelector}}"/> + <fillField stepKey="fillTitleForPayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.titleForPayPalThroughBraintree}}" userInput="{{BraintreeConfigurationData.titleForPayPalThroughBraintree}}"/> + <click stepKey="expandPaymentAction" selector="{{BraintreeConfiguraionSection.paymentActionInPayPal}}"/> + <click stepKey="chooseAuthorize" selector="{{BraintreeConfiguraionSection.actionAuthorize}}"/> + <click stepKey="save" selector="{{BraintreeConfiguraionSection.save}}"/> + <waitForElementVisible selector="{{BraintreeConfiguraionSection.successfulMessage}}" stepKey="waitForSuccessfullyConfigured" time="10"/> + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml new file mode 100644 index 0000000000000..a68042127ec48 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml @@ -0,0 +1,44 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateCustomerActionGroup"> + <click stepKey="openCustomers" selector="{{AdminMenuSection.customers}}"/> + <waitForAjaxLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnAllCustomers" selector="{{CustomersSubmenuSection.allCustomers}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="addNewCustomer" selector="{{CustomersPageSection.addNewCustomerButton}}"/> + <waitForPageLoad stepKey="waitForNewProductPage" time="10"/> + <click stepKey="AssociateToWebsite" selector="{{NewCustomerPageSection.associateToWebsite}}"/> + <click stepKey="Group" selector="{{NewCustomerPageSection.group}}"/> + <fillField stepKey="FillFirstName" selector="{{NewCustomerPageSection.firstName}}" userInput="{{NewCustomerData.FirstName}}"/> + <fillField stepKey="FillLastName" selector="{{NewCustomerPageSection.lastName}}" userInput="{{NewCustomerData.LastName}}"/> + <fillField stepKey="FillEmail" selector="{{NewCustomerPageSection.email}}" userInput="{{NewCustomerData.Email}}"/> + <scrollToTopOfPage stepKey="scrollToAddresses"/> + <click stepKey="goToAddresses" selector="{{NewCustomerPageSection.addresses}}"/> + <waitForAjaxLoad stepKey="waitForAddresses" time="5"/> + <click stepKey="AddNewAddress" selector="{{NewCustomerPageSection.addNewAddress}}"/> + <waitForPageLoad stepKey="waitForAddressFields" time="5"/> + <click stepKey="thickBillingAddress" selector="{{NewCustomerPageSection.defaultBillingAddress}}"/> + <click stepKey="thickShippingAddress" selector="{{NewCustomerPageSection.defaultShippingAddress}}"/> + <fillField stepKey="fillFirstNameForAddress" selector="{{NewCustomerPageSection.firstNameForAddress}}" userInput="{{NewCustomerData.AddressFirstName}}"/> + <fillField stepKey="fillLastNameForAddress" selector="{{NewCustomerPageSection.lastNameForAddress}}" userInput="{{NewCustomerData.AddressLastName}}"/> + <fillField stepKey="fillStreetAddress" selector="{{NewCustomerPageSection.streetAddress}}" userInput="{{NewCustomerData.StreetAddress}}"/> + <fillField stepKey="fillCity" selector="{{NewCustomerPageSection.city}}" userInput="{{NewCustomerData.City}}"/> + <click stepKey="openCountry" selector="{{NewCustomerPageSection.country}}"/> + <waitForAjaxLoad stepKey="waitForCountryList" time="5"/> + <click stepKey="chooseCountry" selector="{{NewCustomerPageSection.countryArmenia}}"/> + <fillField stepKey="fillZip" selector="{{NewCustomerPageSection.zip}}" userInput="{{NewCustomerData.Zip}}"/> + <fillField stepKey="fillPhoneNumber" selector="{{NewCustomerPageSection.phoneNumber}}" userInput="{{NewCustomerData.PhoneNumber}}"/> + <waitForPageLoad stepKey="wait" time="10"/> + <click stepKey="save" selector="{{NewCustomerPageSection.saveCustomer}}"/> + <waitForPageLoad stepKey="waitForCustomersPage" time="10"/> + <waitForElementVisible selector="{{NewCustomerPageSection.createdSuccessMessage}}" stepKey="waitForSuccessfullyCreatedMessage" time="20"/> + + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml new file mode 100644 index 0000000000000..ee7158c2b63f7 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml @@ -0,0 +1,66 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="CreateNewOrderActionGroup"> + <click stepKey="createNewOrder" selector="{{NewOrderSection.createNewOrder}}"/> + <waitForPageLoad stepKey="waitForCustomersList" time="3"/> + <click stepKey="chooseCustomer" selector="{{NewOrderSection.customer}}"/> + <waitForPageLoad stepKey="waitForOrderPage" time="3"/> + <click stepKey="addProducts" selector="{{NewOrderSection.addProducts}}"/> + <waitForPageLoad stepKey="waitForProducts" time="3"/> + <click stepKey="chooseProducts" selector="{{NewOrderSection.chooseProduct}}"/> + <waitForPageLoad stepKey="waitForProductChoose" time="3"/> + <click stepKey="addSelectedProduct" selector="{{NewOrderSection.addSelectedProduct}}"/> + <waitForAjaxLoad stepKey="waitForChoose" time="3"/> + <click stepKey="openAddresses" selector="{{NewOrderSection.openAddresses}}"/> + <click stepKey="chooseAddress" selector="{{NewOrderSection.chooseAddress}}"/> + <fillField stepKey="fillState" selector="{{NewOrderSection.state}}" userInput="Yerevan"/> + <scrollTo stepKey="scrollTo" selector="#order-methods"/> + <waitForPageLoad stepKey="waitForMethods" time="3"/> + <click stepKey="startJSMethodExecution" selector="{{NewOrderSection.openShippingMethods}}"/> + <waitForPageLoad stepKey="waitForJSMethodExecution" time="3"/> + <click stepKey="openShippingMethods" selector="{{NewOrderSection.openShippingMethods}}"/> + <waitForPageLoad stepKey="waitForShippingMethods" time="3"/> + <click stepKey="chooseShippingMethods" selector="{{NewOrderSection.shippingMethod}}"/> + <waitForPageLoad stepKey="waitForShippingMethodChoose" time="4"/> + <click stepKey="chooseBraintree" selector="{{NewOrderSection.creditCardBraintree}}"/> + <waitForPageLoad stepKey="waitForBraintreeConfigs" time="5"/> + <click stepKey="openCardTypes" selector="{{NewOrderSection.openCardTypes}}"/> + <waitForPageLoad stepKey="waitForCardTypes" time="3"/> + <click stepKey="chooseCardType" selector="{{NewOrderSection.masterCard}}"/> + <waitForPageLoad stepKey="waitForCardSelected" time="3"/> + + <switchToIFrame stepKey="switchToCardNumber" selector="{{NewOrderSection.cardFrame}}"/> + <fillField stepKey="fillCardNumber" selector="{{NewOrderSection.creditCardNumber}}" userInput="{{PaymentAndShippingInfo.cardNumber}}"/> + <waitForPageLoad stepKey="waitForFillCardNumber" time="1"/> + <switchToIFrame stepKey="switchBackFromCard"/> + + <switchToIFrame stepKey="switchToExpirationMonth" selector="{{NewOrderSection.monthFrame}}"/> + <fillField stepKey="fillMonth" selector="{{NewOrderSection.expirationMonth}}" userInput="{{PaymentAndShippingInfo.month}}"/> + <waitForPageLoad stepKey="waitForFillMonth" time="1"/> + <switchToIFrame stepKey="switchBackFromMonth"/> + + <switchToIFrame stepKey="switchToExpirationYear" selector="{{NewOrderSection.yearFrame}}"/> + <fillField stepKey="fillYear" selector="{{NewOrderSection.expirationYear}}" userInput="{{PaymentAndShippingInfo.year}}"/> + <waitForPageLoad stepKey="waitForFillYear" time="1"/> + <switchToIFrame stepKey="switchBackFromYear"/> + + <switchToIFrame stepKey="switchToCVV" selector="{{NewOrderSection.cvvFrame}}"/> + <fillField stepKey="fillCVV" selector="{{NewOrderSection.cvv}}" userInput="{{PaymentAndShippingInfo.cvv}}"/> + <wait stepKey="waitForFillCVV" time="1"/> + <switchToIFrame stepKey="switchBackFromCVV"/> + + <click stepKey="submitOrder" selector="{{NewOrderSection.submitOrder}}"/> + <waitForPageLoad stepKey="waitForSaveConfig" time="5"/> + <waitForElementVisible selector="{{NewOrderSection.successMessage}}" stepKey="waitForSuccessMessage" time="1"/> + + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml new file mode 100644 index 0000000000000..19de3e859ae9a --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml @@ -0,0 +1,25 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateNewProductActionGroup"> + + <click stepKey="openCatalog" selector="{{AdminMenuSection.catalog}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnProducts" selector="{{CatalogSubmenuSection.products}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="addProduct" selector="{{ProductsPageSection.addProductButton}}"/> + <waitForPageLoad stepKey="waitForNewProductPage" time="10"/> + <fillField stepKey="FillProductName" selector="{{NewProductPageSection.productName}}" userInput="{{NewProductData.ProductName}}"/> + <fillField stepKey="FillPrice" selector="{{NewProductPageSection.price}}" userInput="{{NewProductData.Price}}"/> + <fillField stepKey="FillQuantity" selector="{{NewProductPageSection.quantity}}" userInput="{{NewProductData.Quantity}}"/> + <click stepKey="Save" selector="{{NewProductPageSection.saveButton}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyCreatedMessage" selector="{{NewProductPageSection.createdSuccessMessage}}" time="10"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml new file mode 100644 index 0000000000000..65eddc0d9e51a --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml @@ -0,0 +1,28 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteCustomerActionGroup"> + <arguments> + <argument name="lastName" defaultValue=""/> + </arguments> + <click stepKey="openCustomers" selector="{{AdminMenuSection.customers}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="10"/> + <click stepKey="clickOnAllCustomers" selector="{{CustomersSubmenuSection.allCustomers}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="chooseCustomer" selector="{{CustomersPageSection.customerCheckbox(lastName)}}"/> + <waitForAjaxLoad stepKey="waitForThick" time="2"/> + <click stepKey="OpenActions" selector="{{CustomersPageSection.actions}}"/> + <waitForAjaxLoad stepKey="waitForDelete" time="5"/> + <click stepKey="ChooseDelete" selector="{{CustomersPageSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> + <click stepKey="clickOnOk" selector="{{CustomersPageSection.ok}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{CustomersPageSection.deletedSuccessMessage}}" time="10"/> + + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml new file mode 100644 index 0000000000000..724c6d92846c4 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteProductActionGroup"> + <arguments> + <argument name="productName" defaultValue=""/> + </arguments> + <click stepKey="openCatalog" selector="{{AdminMenuSection.catalog}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnProducts" selector="{{CatalogSubmenuSection.products}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="TickCheckbox" selector="{{ProductsPageSection.checkboxForProduct(productName)}}"/> + <click stepKey="OpenActions" selector="{{ProductsPageSection.actions}}"/> + <waitForAjaxLoad stepKey="waitForDelete" time="5"/> + <click stepKey="ChooseDelete" selector="{{ProductsPageSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> + <click stepKey="clickOnOk" selector="{{ProductsPageSection.ok}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{ProductsPageSection.deletedSuccessMessage}}" time="10"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml new file mode 100644 index 0000000000000..bc6d6c2b46dc9 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml @@ -0,0 +1,29 @@ +<?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/testSchema.xsd"> + <actionGroup name="StorefrontFillCartDataActionGroup"> + <arguments> + <argument name="cartData" defaultValue="PaymentAndShippingInfo"/> + </arguments> + <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.cartFrame}}" stepKey="switchToIframe"/> + <fillField selector="{{BraintreeConfigurationPaymentSection.cartCode}}" userInput="{{cartData.cardNumber}}" stepKey="setCartCode"/> + <switchToIFrame stepKey="switchBack"/> + <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.monthFrame}}" stepKey="switchToIframe1"/> + <fillField selector="{{BraintreeConfigurationPaymentSection.month}}" userInput="{{cartData.month}}" stepKey="setMonth"/> + <switchToIFrame stepKey="switchBack1"/> + <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.yearFrame}}" stepKey="switchToIframe2"/> + <fillField selector="{{BraintreeConfigurationPaymentSection.year}}" userInput="{{cartData.year}}" stepKey="setYear"/> + <switchToIFrame stepKey="switchBack2"/> + <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.codeFrame}}" stepKey="switchToIframe3"/> + <fillField selector="{{BraintreeConfigurationPaymentSection.verificationNumber}}" userInput="{{cartData.cvv}}" stepKey="setVerificationNumber"/> + <switchToIFrame stepKey="SwitchBackToWindow"/> + + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml new file mode 100644 index 0000000000000..7c774a634b369 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml @@ -0,0 +1,28 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Sign out--> + <actionGroup name="SignOut"> + <click selector="{{SignOutSection.admin}}" stepKey="clickToAdminProfile"/> + <click selector="{{SignOutSection.logout}}" stepKey="clickToLogOut"/> + <waitForPageLoad stepKey="waitForPageLoad" /> + <see userInput="You have logged out." stepKey="seeSuccessMessage" /> + <waitForElementVisible selector="//*[@data-ui-id='messages-message-success']" stepKey="waitForSuccessMessageLoggedOut" time="5"/> + </actionGroup> + + <!--Login New User--> + <actionGroup name="LoginNewUser"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> + <fillField userInput="{{NewAdmin.username}}" selector="{{LoginFormSection.username}}" stepKey="fillUsername"/> + <fillField userInput="{{NewAdmin.password}}" selector="{{LoginFormSection.password}}" stepKey="fillPassword"/> + <click selector="{{LoginFormSection.signIn}}" stepKey="clickLogin"/> + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml new file mode 100644 index 0000000000000..8f2588a6effa5 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml @@ -0,0 +1,150 @@ +<?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="SampleBraintreeConfig" type="braintree_config_state"> + <requiredEntity type="title">SampleTitle</requiredEntity> + <requiredEntity type="payment_action">SamplePaymentAction</requiredEntity> + <requiredEntity type="environment">SampleEnvironment</requiredEntity> + <requiredEntity type="merchant_id">SampleMerchantId</requiredEntity> + <requiredEntity type="public_key">SamplePublicKey</requiredEntity> + <requiredEntity type="private_key">SamplePrivateKey</requiredEntity> + </entity> + <entity name="SampleTitle" type="title"> + <data key="value">Sample Braintree Config</data> + </entity> + <entity name="SamplePaymentAction" type="payment_action"> + <data key="value">authorize</data> + </entity> + <entity name="SampleEnvironment" type="environment"> + <data key="value">sandbox</data> + </entity> + <entity name="SampleMerchantId" type="merchant_id"> + <data key="value">someMerchantId</data> + </entity> + <entity name="SamplePublicKey" type="public_key"> + <data key="value">somePublicKey</data> + </entity> + <entity name="SamplePrivateKey" type="private_key"> + <data key="value">somePrivateKey</data> + </entity> + + <entity name="BraintreeConfig" type="braintree_config_state"> + <requiredEntity type="title">BraintreeTitle</requiredEntity> + <requiredEntity type="payment_action">PaymentAction</requiredEntity> + <requiredEntity type="environment">Environment</requiredEntity> + <requiredEntity type="merchant_id">MerchantId</requiredEntity> + <requiredEntity type="public_key">PublicKey</requiredEntity> + <requiredEntity type="private_key">PrivateKey</requiredEntity> + </entity> + <entity name="BraintreeTitle" type="title"> + <data key="value">Credit Card (Braintree)</data> + </entity> + <entity name="PaymentAction" type="payment_action"> + <data key="value">authorize</data> + </entity> + <entity name="Environment" type="environment"> + <data key="value">sandbox</data> + </entity> + <entity name="MerchantId" type="merchant_id"> + <data key="value">d4pdjhxgjfrsmzbf</data> + </entity> + <entity name="PublicKey" type="public_key"> + <data key="value">m7q4wmh43xrgyrst</data> + </entity> + <entity name="PrivateKey" type="private_key"> + <data key="value">67de364080b1b4e2492d7a3de413a572</data> + </entity> + + <!-- default configuration used to restore Magento config --> + <entity name="DefaultBraintreeConfig" type="braintree_config_state"> + <requiredEntity type="title">DefaultTitle</requiredEntity> + <requiredEntity type="payment_action">DefaultPaymentAction</requiredEntity> + <requiredEntity type="environment">DefaultEnvironment</requiredEntity> + <requiredEntity type="merchant_id">DefaultMerchantId</requiredEntity> + <requiredEntity type="public_key">DefaultPublicKey</requiredEntity> + <requiredEntity type="private_key">DefaultPrivateKey</requiredEntity> + </entity> + <entity name="DefaultTitle" type="title"> + <data key="value"/> + </entity> + <entity name="DefaultPaymentAction" type="payment_action"> + <data key="value"/> + </entity> + <entity name="DefaultEnvironment" type="environment"> + <data key="value"/> + </entity> + <entity name="DefaultMerchantId" type="merchant_id"> + <data key="value"/> + </entity> + <entity name="DefaultPublicKey" type="public_key"> + <data key="value"/> + </entity> + <entity name="DefaultPrivateKey" type="private_key"> + <data key="value"/> + </entity> + + <entity name="CustomBraintreeConfigurationData" type="custom_braintree_config_state"> + <requiredEntity type="braintree_cc_vault_active">BraintreeValuteActive</requiredEntity> + <requiredEntity type="active">EnableSolution</requiredEntity> + </entity> + <entity name="BraintreeValuteActive" type="braintree_cc_vault_active"> + <data key="value">1</data> + </entity> + <entity name="EnableSolution" type="active"> + <data key="value">1</data> + </entity> + + <entity name="RollBackCustomBraintreeConfigurationData" type="custom_braintree_config_state"> + <requiredEntity type="braintree_cc_vault_active">DefaultBraintreeValuteActive</requiredEntity> + <requiredEntity type="active">DefaultEnableSolution</requiredEntity> + </entity> + <entity name="DefaultBraintreeValuteActive" type="braintree_cc_vault_active"> + <data key="value">0</data> + </entity> + <entity name="DefaultEnableSolution" type="active"> + <data key="value">0</data> + </entity> + + <entity name="testData" type="data"> + <data key="websiteName" unique="suffix">Website</data> + <data key="websiteCode" unique="suffix">new_website</data> + <data key="name" unique="suffix">Store</data> + <data key="storeCode" unique="suffix">new_store</data> + <data key="block" unique="suffix">Block</data> + </entity> + + <entity name="role" type="data"> + <data key="name" unique="suffix">Role</data> + </entity> + <entity name="NewAdmin" type="user"> + <data key="username" unique="suffix">admin</data> + <data key="firstName">John</data> + <data key="lastName">Smith</data> + <data key="password">admin123</data> + <data key="email">mail@mail.com</data> + </entity> + + <entity name="PaymentAndShippingInfo" type="data"> + <data key="cardNumber">5105105105105100</data> + <data key="month">12</data> + <data key="year">20</data> + <data key="cvv">113</data> + </entity> + + <entity name="BraintreeConfigurationData" type="data"> + <data key="title">Credit Card (Braintree)</data> + <data key="merchantID">d4pdjhxgjfrsmzbf</data> + <data key="publicKey">m7q4wmh43xrgyrst</data> + <data key="privateKey">67de364080b1b4e2492d7a3de413a572</data> + <data key="merchantAccountID">Magneto</data> + <data key="titleForPayPalThroughBraintree">PayPal (Braintree)</data> + </entity> + +</entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml new file mode 100644 index 0000000000000..772c1c39a04ca --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml @@ -0,0 +1,23 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewCustomerData" type="braintree_config_state"> + <data key="FirstName">Abgar</data> + <data key="LastName">Abgaryan</data> + <data key="Email">m@m.com</data> + <data key="AddressFirstName">Abgar</data> + <data key="AddressLastName">Abgaryan</data> + <data key="StreetAddress">Street</data> + <data key="City">Yerevan</data> + <data key="Zip">9999</data> + <data key="PhoneNumber">9999</data> + </entity> + +</entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml new file mode 100644 index 0000000000000..72661ae94076f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewProductData" type="braintree_config_state"> + <data key="ProductName">ProductTest</data> + <data key="Price">100</data> + <data key="Quantity">100</data> + </entity> + +</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CacheInvalidate/LICENSE.txt b/app/code/Magento/Braintree/Test/Mftf/LICENSE.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CacheInvalidate/LICENSE.txt rename to app/code/Magento/Braintree/Test/Mftf/LICENSE.txt diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/BundleImportExport/LICENSE_AFL.txt b/app/code/Magento/Braintree/Test/Mftf/LICENSE_AFL.txt similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/BundleImportExport/LICENSE_AFL.txt rename to app/code/Magento/Braintree/Test/Mftf/LICENSE_AFL.txt diff --git a/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml new file mode 100644 index 0000000000000..5e3b870d65c67 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml @@ -0,0 +1,63 @@ +<?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="CreateBraintreeConfigState" dataType="braintree_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> + <object key="groups" dataType="braintree_config_state"> + <object key="braintree_section" dataType="braintree_config_state"> + <object key="groups" dataType="braintree_config_state"> + <object key="braintree" dataType="braintree_config_state"> + <object key="groups" dataType="braintree_config_state"> + <object key="braintree_required" dataType="braintree_config_state"> + <object key="fields" dataType="braintree_config_state"> + <object key="title" dataType="title"> + <field key="value">string</field> + </object> + <object key="environment" dataType="environment"> + <field key="value">string</field> + </object> + <object key="payment_action" dataType="payment_action"> + <field key="value">string</field> + </object> + <object key="merchant_id" dataType="merchant_id"> + <field key="value">string</field> + </object> + <object key="public_key" dataType="public_key"> + <field key="value">string</field> + </object> + <object key="private_key" dataType="private_key"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + </operation> + <operation name="CustomBraintreeConfigState" dataType="custom_braintree_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> + <object key="groups" dataType="custom_braintree_config_state"> + <object key="braintree_section" dataType="custom_braintree_config_state"> + <object key="groups" dataType="custom_braintree_config_state"> + <object key="braintree" dataType="custom_braintree_config_state"> + <object key="fields" dataType="custom_braintree_config_state"> + <object key="braintree_cc_vault_active" dataType="braintree_cc_vault_active"> + <field key="value">integer</field> + </object> + <object key="active" dataType="active"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Braintree/Test/Mftf/README.md b/app/code/Magento/Braintree/Test/Mftf/README.md new file mode 100644 index 0000000000000..6ee177a9cdcd2 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Braintree Functional Tests + +The Functional Test Module for **Magento Braintree** module. diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml new file mode 100644 index 0000000000000..1158f471d51f0 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCreateRoleSection"> + <element name="create" type="button" selector="#add"/> + <element name="name" type="button" selector="#role_name"/> + <element name="password" type="input" selector="#current_password"/> + <element name="roleResources" type="button" selector="#role_info_tabs_account"/> + <element name="roleResource" type="button" selector="#gws_is_all"/> + <element name="resourceValue" type="button" selector="//*[text()='{{arg1}}']" parameterized="true"/> + <element name="roleScope" type="button" selector="#all"/> + <element name="scopeValue" type="button" selector="//select[@id='all']/*[text()='{{arg2}}']" parameterized="true"/> + <element name="website" type="checkbox" selector="//*[contains(text(), '{{arg3}}')]" parameterized="true"/> + <element name="save" type="button" selector="//button[@title='Save Role']"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml new file mode 100644 index 0000000000000..98d748b5a30ea --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCreateUserSection"> + <element name="system" type="input" selector="#menu-magento-backend-system"/> + <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> + <element name="create" type="input" selector="#add"/> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="firstNameTextField" type="input" selector="#user_firstname"/> + <element name="lastNameTextField" type="input" selector="#user_lastname"/> + <element name="emailTextField" type="input" selector="#user_email"/> + <element name="passwordTextField" type="input" selector="#user_password"/> + <element name="pwConfirmationTextField" type="input" selector="#user_confirmation"/> + <element name="currentPasswordField" type="input" selector="#user_current_password"/> + <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> + <element name="saveButton" type="button" selector="#save"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml new file mode 100644 index 0000000000000..220c9a444b02f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminDeleteRoleSection"> + <element name="theRole" selector="//td[contains(text(), 'Role')]" type="button"/> + <element name="current_pass" type="button" selector="#current_password"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete Role')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml new file mode 100644 index 0000000000000..bf2e2b44eb602 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminDeleteUserSection"> + <element name="theUser" selector="//td[contains(text(), 'John')]" type="button"/> + <element name="password" selector="#user_current_password" type="input"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete User')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml new file mode 100644 index 0000000000000..e37ce8f4738b3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -0,0 +1,21 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditRoleInfoSection"> + <element name="roleName" type="input" selector="#role_name"/> + <element name="password" type="input" selector="#current_password"/> + <element name="roleResourcesTab" type="button" selector="#role_info_tabs_account"/> + <element name="backButton" type="button" selector="button[title='Back']"/> + <element name="resetButton" type="button" selector="button[title='Reset']"/> + <element name="deleteButton" type="button" selector="button[title='Delete Role']"/> + <element name="saveButton" type="button" selector="button[title='Save Role']"/> + <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> + <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml new file mode 100644 index 0000000000000..e999413c96d74 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditUserRoleSection"> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml new file mode 100644 index 0000000000000..2e5fcfb7b5c8d --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml @@ -0,0 +1,28 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditUserSection"> + <element name="system" type="input" selector="#menu-magento-backend-system"/> + <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> + <element name="create" type="input" selector="#add"/> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="firstNameTextField" type="input" selector="#user_firstname"/> + <element name="lastNameTextField" type="input" selector="#user_lastname"/> + <element name="emailTextField" type="input" selector="#user_email"/> + <element name="passwordTextField" type="input" selector="#user_password"/> + <element name="pwConfirmationTextField" type="input" selector="#user_confirmation"/> + <element name="currentPasswordField" type="input" selector="#user_current_password"/> + <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + <element name="saveButton" type="button" selector="#save"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml new file mode 100644 index 0000000000000..660c7393b4061 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminMenuSection"> + <element name="dashboard" type="button" selector="//li[@id='menu-magento-backend-dashboard']"/> + <element name="sales" type="button" selector="//li[@id='menu-magento-sales-sales']"/> + <element name="catalog" type="button" selector="//li[@id='menu-magento-catalog-catalog']"/> + <element name="customers" type="button" selector="//li[@id='menu-magento-customer-customer']"/> + <element name="marketing" type="button" selector="//li[@id='//li[@id='menu-magento-backend-marketing']']"/> + <element name="content" type="button" selector="//li[@id='menu-magento-backend-content']"/> + <element name="reports" type="button" selector="//li[@id='menu-magento-reports-report']"/> + <element name="stores" type="button" selector="//li[@id='menu-magento-backend-stores']"/> + <element name="system" type="button" selector="//li[@id='menu-magento-backend-system']"/> + <element name="findPartners" type="button" selector="//li[@id='menu-magento-marketplace-partners']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml new file mode 100644 index 0000000000000..63cbadc71d3d3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminRoleGridSection"> + <element name="idFilterTextField" type="input" selector="#roleGrid_filter_role_id"/> + <element name="roleNameFilterTextField" type="input" selector="#roleGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml new file mode 100644 index 0000000000000..9564bc61f799c --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminUserGridSection"> + <element name="usernameFilterTextField" type="input" selector="#permissionsUserGrid_filter_username"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="usernameInFirstRow" type="text" selector=".col-username"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + <element name="successMessage" type="text" selector=".message-success"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml new file mode 100644 index 0000000000000..016af2e102744 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml @@ -0,0 +1,34 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="BraintreeConfiguraionSection"> + <element name="titleForBraintreeSettings" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_title']"/> + <element name="environment" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_environment']"/> + <element name="sandbox" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_environment']/option[text()='Sandbox']"/> + <element name="paymentActionSelect" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_payment_action']"/> + <element name="paymentAction" type="button" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_payment_action']/option[text()='Authorize']"/> + <element name="merchantID" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_merchant_id']"/> + <element name="publicKey" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_public_key']"/> + <element name="privateKey" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_private_key']"/> + <element name="enableThisSolution" type="select" selector="//select[@id='payment_us_braintree_section_braintree_active']"/> + <element name="yesForEnable" type="button" selector="//select[@id='payment_us_braintree_section_braintree_active']/option[text()='Yes']"/> + <element name="payPalThroughBraintree" type="select" selector="//select[@id='payment_us_braintree_section_braintree_active_braintree_paypal']"/> + <element name="yesForPayPalThroughBraintree" type="input" selector="//select[@id='payment_us_braintree_section_braintree_active_braintree_paypal']/option[text()='Yes']"/> + <element name="advancedBraintreeSettings" type="button" selector="//a[@id='payment_us_braintree_section_braintree_braintree_advanced-head']"/> + <element name="merchantAccountID" type="text" selector="//input[@id='payment_us_braintree_section_braintree_braintree_advanced_merchant_account_id']"/> + <element name="CVVVerification" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_advanced_useccv']"/> + <element name="yesForCVV" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_advanced_useccv']/option[text()='Yes']"/> + <element name="payPalThroughBraintreeSelector" type="text" selector="//a[@id='payment_us_braintree_section_braintree_braintree_paypal-head']"/> + <element name="titleForPayPalThroughBraintree" type="text" selector="//input[@id='payment_us_braintree_section_braintree_braintree_paypal_title']"/> + <element name="paymentActionInPayPal" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_paypal_payment_action']"/> + <element name="actionAuthorize" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_paypal_payment_action']/option[text()='Authorize']"/> + <element name="save" type="button" selector="//span[text()='Save Config']"/> + <element name="successfulMessage" type="text" selector="//*[@data-ui-id='messages-message-success']"/> + + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml new file mode 100644 index 0000000000000..d9f2b14a40e2f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.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:Test/etc/testSchema.xsd"> + <section name="BraintreeConfigurationPaymentSection"> + <element name="creditCart" type="radio" selector="#braintree"/> + <element name="paymentMethodContainer" type="block" selector=".payment-method-braintree"/> + <element name="paymentMethod" type="radio" selector="//div[@class='payment-group']//input[contains(@id, 'braintree_cc_vault_')]"/> + <element name="cartFrame" type="iframe" selector="braintree-hosted-field-number"/> + <element name="monthFrame" type="iframe" selector="braintree-hosted-field-expirationMonth"/> + <element name="yearFrame" type="iframe" selector="braintree-hosted-field-expirationYear"/> + <element name="codeFrame" type="iframe" selector="braintree-hosted-field-cvv"/> + <element name="cartCode" type="input" selector="#credit-card-number"/> + <element name="month" type="input" selector="#expiration-month"/> + <element name="year" type="input" selector="#expiration-year"/> + <element name="verificationNumber" type="input" selector="#cvv"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml new file mode 100644 index 0000000000000..32f02a69f817e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CatalogSubmenuSection"> + <element name="products" type="button" selector="//li[@id='menu-magento-catalog-catalog']//li[@data-ui-id='menu-magento-catalog-catalog-products']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml new file mode 100644 index 0000000000000..100407438eaae --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ConfigurationListSection"> + <element name="sales" type="button" selector="//div[contains(@class, 'admin__page-nav-title title _collapsible')]/strong[text()='Sales']"/> + <element name="salesPaymentMethods" type="button" selector="//span[text()='Payment Methods']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml new file mode 100644 index 0000000000000..885a45be721f1 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ConfigurationPaymentSection"> + <element name="configureButton" type="button" selector="//button[@id='payment_us_braintree_section_braintree-head']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml new file mode 100644 index 0000000000000..e4a75b1b6a842 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml @@ -0,0 +1,19 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CustomersPageSection"> + <element name="addNewCustomerButton" type="button" selector="//*[@id='add']"/> + <element name="customerCheckbox" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> + <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> + <element name="delete" type="button" selector="//*[contains(@class,'admin__data-grid-header-row row row-gutter')]//*[text()='Delete']"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + <element name="deletedSuccessMessage" type="button" selector="//*[@class='message message-success success']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml new file mode 100644 index 0000000000000..937afb83da96f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CustomersSubmenuSection"> + <element name="allCustomers" type="button" selector="//li[@id='menu-magento-customer-customer']//li[@data-ui-id='menu-magento-customer-customer-manage']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml new file mode 100644 index 0000000000000..d302f9c7d0cba --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml @@ -0,0 +1,33 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewCustomerPageSection"> + <element name="associateToWebsite" type="select" selector="//*[@class='admin__field-control _with-tooltip']//*[@class='admin__control-select']"/> + <element name="group" type="select" selector="//div[@class='admin__field-control admin__control-fields required']//div[@class='admin__field-control']//select[@class='admin__control-select']"/> + <element name="firstName" type="input" selector="//input[@name='customer[firstname]']"/> + <element name="lastName" type="input" selector="//input[@name='customer[lastname]']"/> + <element name="email" type="input" selector="//input[@name='customer[email]']"/> + <element name="addresses" type="button" selector="//a[@id='tab_address']"/> + <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="countryArmenia" type="select" selector="//select[contains(@name, 'country_id')]//option[@data-title='Armenia']"/> + <element name="zip" type="input" selector="//input[contains(@name, 'postcode')]"/> + <element name="phoneNumber" type="input" selector="//input[contains(@name, 'telephone')]"/> + <element name="saveCustomer" type="button" selector="//button[@title='Save Customer']"/> + <element name="createdSuccessMessage" type="button" selector="//div[@data-ui-id='messages-message-success']"/> + + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml new file mode 100644 index 0000000000000..13f59ad2cf18e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml @@ -0,0 +1,35 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewOrderSection"> + <element name="createNewOrder" type="button" selector="#add"/> + <element name="customer" type="button" selector="//td[contains(text(), 'Abgar')]"/> + <element name="addProducts" type="button" selector="#add_products"/> + <element name="chooseProduct" type="button" selector="//td[contains(text(),'ProductTest')]"/> + <element name="addSelectedProduct" type="button" selector="//button[@title='Add Selected Product(s) to Order']"/> + <element name="openAddresses" type="button" selector="#order-billing_address_customer_address_id"/> + <element name="chooseAddress" type="button" selector="//select[@id='order-billing_address_customer_address_id']//option[contains(text(), 'Abgar')]"/> + <element name="state" type="button" selector="#order-billing_address_region"/> + <element name="openShippingMethods" type="button" selector="//a[@class='action-default']"/> + <element name="shippingMethod" type="button" selector="//label[@class='admin__field-label' and @for='s_method_flatrate_flatrate']"/> + <element name="creditCardBraintree" type="button" selector="#p_method_braintree"/> + <element name="openCardTypes" type="button" selector="#braintree_cc_type"/> + <element name="masterCard" type="button" selector="//option[contains(text(), 'MasterCard')]"/> + <element name="cardFrame" type="iframe" selector="braintree-hosted-field-number"/> + <element name="monthFrame" type="iframe" selector="braintree-hosted-field-expirationMonth"/> + <element name="yearFrame" type="iframe" selector="braintree-hosted-field-expirationYear"/> + <element name="cvvFrame" type="iframe" selector="braintree-hosted-field-cvv"/> + <element name="creditCardNumber" type="input" selector="#credit-card-number"/> + <element name="expirationMonth" type="input" selector="#expiration-month"/> + <element name="expirationYear" type="input" selector="#expiration-year"/> + <element name="cvv" type="input" selector="#cvv"/> + <element name="submitOrder" type="input" selector="#submit_order_top_button"/> + <element name="successMessage" type="input" selector="#messages"/> + + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml new file mode 100644 index 0000000000000..42e451940c91b --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml @@ -0,0 +1,19 @@ +<?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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewProductPageSection"> + <element name="productName" type="input" selector="//input[@name='product[name]']"/> + <element name="sku" type="input" selector="//input[@name='product[sku]']"/> + <element name="price" type="input" selector="//input[@name='product[price]']"/> + <element name="quantity" type="input" selector="//input[@name='product[quantity_and_stock_status][qty]']"/> + <element name="saveButton" type="button" selector="//button[@id='save-button']"/> + <element name="createdSuccessMessage" type="button" selector="//div[@data-ui-id='messages-message-success']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml new file mode 100644 index 0000000000000..267efdf3d0e5e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ProductsPageSection"> + <element name="addProductButton" type="button" selector="//button[@id='add_new_product-button']"/> + <element name="checkboxForProduct" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> + <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> + <element name="delete" type="button" selector="//*[contains(@class,'admin__data-grid-header-row row row-gutter')]//*[text()='Delete']"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + <element name="deletedSuccessMessage" type="button" selector="//*[@class='message message-success success']"/> + + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml new file mode 100644 index 0000000000000..f094baa9f3446 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="StoresSubmenuSection"> + <element name="configuration" type="button" selector="//li[@id='menu-magento-backend-stores']//li[@data-ui-id='menu-magento-config-system-config']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml new file mode 100644 index 0000000000000..3a07cbc6dd145 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + + <section name="LoginFormSection"> + <element name="username" type="input" selector="#username"/> + <element name="password" type="input" selector="#login"/> + <element name="signIn" type="button" selector=".actions .action-primary" timeout="30"/> + </section> + + <section name="SignOutSection"> + <element name="admin" type="button" selector=".admin__action-dropdown-text"/> + <element name="logout" type="button" selector="//*[contains(text(), 'Sign Out')]"/> + </section> + +</sections> + diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml new file mode 100644 index 0000000000000..114c79189101a --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -0,0 +1,110 @@ +<?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="BraintreeCreditCardOnCheckoutTest"> + <annotations> + <features value="Braintree"/> + <stories value="MAGETWO-91624 - Braintree saved cards use billing address the same as shipping"/> + <title value="Use saved for Braintree credit card on checkout with selecting billing address"/> + <description value="Use saved for Braintree credit card on checkout with selecting billing address"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93767"/> + <group value="braintree"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="customer"/> + <createData entity="BraintreeConfig" stepKey="BraintreeConfigurationData"/> + <createData entity="CustomBraintreeConfigurationData" stepKey="CustomBraintreeConfigurationData"/> + </before> + + <after> + <deleteData createDataKey="product" stepKey="deleteProduct1"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <createData entity="DefaultBraintreeConfig" stepKey="DefaultBraintreeConfig"/> + <createData entity="RollBackCustomBraintreeConfigurationData" stepKey="RollBackCustomBraintreeConfigurationData"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="StorefrontSignOutActionGroup"/> + </after> + <!--Go to storefront--> + <amOnPage url="" stepKey="DoToStorefront"/> + <!--Create account--> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="SignUpNewUserFromStorefrontActionGroup"> + <argument name="Customer" value="Simple_US_Customer"/> + </actionGroup> + + <!--Add product to cart--> + <amOnPage url="$$product.sku$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <!--Proceed to checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> + + <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="LoggedInCheckoutFillNewBillingAddressActionGroup"> + <argument name="Address" value="US_Address_CA"/> + </actionGroup> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <!--Fill cart data--> + <click selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="SelectBraintreePaymentMethod"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <scrollTo selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="ScrollToCreditCardSection"/> + <actionGroup ref="StorefrontFillCartDataActionGroup" stepKey="StorefrontFillCartDataActionGroup"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <!--Place order--> + <click selector="{{BraintreeConfigurationPaymentSection.paymentMethodContainer}} {{CheckoutPaymentSection.placeOrder}}" + stepKey="PlaceOrder"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!--Add product to cart again--> + <amOnPage url="$$product.sku$$.html" stepKey="goToProductPage1"/> + <waitForPageLoad stepKey="waitForPageLoad6"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart1"/> + <waitForPageLoad stepKey="waitForPageLoad7"/> + <!--Proceed to checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup1"/> + <click selector="{{CheckoutPaymentSection.addressAction('New Address')}}" stepKey="clickOnNewAddress"/> + <waitForPageLoad stepKey="waitForPageLoad8"/> + <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="LoggedInCheckoutFillNewBillingAddressActionGroup1"> + <argument name="Address" value="US_Address_NY"/> + </actionGroup> + <click selector="{{CheckoutPaymentSection.addressAction('Save Address')}}" stepKey="SaveAddress"/> + <waitForPageLoad stepKey="waitForPageLoad9"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext1"/> + <waitForPageLoad stepKey="waitForPageLoad10"/> + <click selector="{{BraintreeConfigurationPaymentSection.paymentMethod}}" stepKey="SelectBraintreePaymentMethod1"/> + <waitForPageLoad stepKey="waitForPageLoad11"/> + <click selector="{{CheckoutPaymentSection.shippingAndBillingAddressSame}}" stepKey="UncheckCheckBox"/> + + <click selector="{{CheckoutShippingSection.updateAddress}}" stepKey="clickToUpdate"/> + <waitForPageLoad stepKey="waitForPageLoad12"/> + <!--Place order--> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="PlaceOrder1"/> + <waitForPageLoad stepKey="waitForPageLoad13"/> + + <click selector="{{CheckoutOrderSummarySection.orderNumber}}" stepKey="ClickOnOrderNumber"/> + <waitForPageLoad stepKey="waitForPageLoad14"/> + <!--Check billing and shipping addresses also additional Address info--> + <click selector="{{CheckoutPaymentSection.addressBook}}" stepKey="goToAddressBook"/> + <grabTextFrom selector="{{CheckoutOrderSummarySection.shippingAddress}}" stepKey="shippingAddr"/> + <grabTextFrom selector="{{CheckoutOrderSummarySection.billingAddress}}" stepKey="billingAddr"/> + <grabTextFrom selector="{{CheckoutOrderSummarySection.additionalAddress}}" stepKey="additionalAddress"/> + <see userInput="Shipping Address" stepKey="seeShippingAddress"/> + <see userInput="Billing Address" stepKey="seeBillingAddress"/> + <assertEquals stepKey="assertValuesAreEqual" actual="$billingAddr" expected="$shippingAddr"/> + <assertNotEquals stepKey="assertValuesAreNotEqual" actual="$billingAddr" expected="$additionalAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml new file mode 100644 index 0000000000000..df2e98816f0d3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.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="CreateAnAdminOrderUsingBraintreePaymentTest1"> + <annotations> + <features value="Backend"/> + <stories value="Creation an admin order using Braintree payment"/> + <title value="Create order using Braintree payment"/> + <description value="Admin should be able to create order using Braintree payment"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-93677"/> + <group value="braintree"/> + <skip> + <issueId value="MQE-1187" /> + </skip> + </annotations> + + + <before> + <!--Login As Admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--CreateNewProduct--> + <actionGroup ref="CreateNewProductActionGroup" stepKey="CreateNewProduct"/> + + <!--Create New Customer--> + <actionGroup ref="CreateCustomerActionGroup" stepKey="CreateCustomer"/> + + </before> + + + <!--Configure Braintree--> + <actionGroup ref="ConfigureBraintree" stepKey="configureBraintree"/> + + <!--Create New Role--> + <actionGroup ref="GoToUserRoles" stepKey="GoToUserRoles"/> + <actionGroup ref="AdminCreateRole" stepKey="AdminCreateNewRole"/> + + <!--Create New User With Specific Role--> + <actionGroup ref="GoToAllUsers" stepKey="GoToAllUsers"/> + <actionGroup ref="AdminCreateUserAction" stepKey="AdminCreateNewUser"/> + + <!--SignOut--> + <actionGroup ref="SignOut" stepKey="signOutFromAdmin"/> + + <!--SignIn New User--> + <actionGroup ref="LoginNewUser" stepKey="signInNewUser"/> + <waitForPageLoad stepKey="waitForLogin" time="3"/> + + <!--Create New Order--> + <actionGroup ref="CreateNewOrderActionGroup" stepKey="createNewOrder"/> + + + <after> + <!--SignOut--> + <actionGroup ref="SignOut" stepKey="signOutFromNewUser"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Delete Product--> + <actionGroup ref="DeleteProductActionGroup" stepKey="DeleteAllProducts"> + <argument name="productName" value="NewProductData.ProductName"/> + </actionGroup> + + <!--Delete Customer--> + <actionGroup ref="DeleteCustomerActionGroup" stepKey="DeleteCustomer"> + <argument name="lastName" value="NewCustomerData.LastName"/> + </actionGroup> + + <!--Delete User --> + <actionGroup ref="GoToAllUsers" stepKey="GoBackToAllUsers"/> + <actionGroup ref="AdminDeleteUserActionGroup" stepKey="AdminDeleteUserActionGroup"/> + + <!--Delete Role--> + <actionGroup ref="GoToUserRoles" stepKey="GoBackToUserRoles"/> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="AdminDeleteRoleActionGroup"/> + + </after> + + </test> +</tests> diff --git a/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php b/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php index 5b28e6d8aedcd..fa5512ca2db9f 100644 --- a/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Block/FormTest.php @@ -13,13 +13,11 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Payment\Helper\Data; use Magento\Payment\Model\Config; -use Magento\Store\Api\Data\StoreInterface; -use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Model\VaultPaymentInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class FormTest + * Tests \Magento\Braintree\Block\Form. */ class FormTest extends \PHPUnit\Framework\TestCase { @@ -45,36 +43,35 @@ class FormTest extends \PHPUnit\Framework\TestCase /** * @var Quote|MockObject */ - private $sessionQuote; + private $sessionQuoteMock; /** * @var Config|MockObject */ - private $gatewayConfig; + private $gatewayConfigMock; /** * @var CcType|MockObject */ - private $ccType; + private $ccTypeMock; /** - * @var StoreManagerInterface|MockObject + * @var Data|MockObject */ - private $storeManager; + private $paymentDataHelperMock; /** - * @var Data|MockObject + * @var string */ - private $paymentDataHelper; + private $storeId = '1'; protected function setUp() { $this->initCcTypeMock(); $this->initSessionQuoteMock(); $this->initGatewayConfigMock(); - - $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); - $this->paymentDataHelper = $this->getMockBuilder(Data::class) + + $this->paymentDataHelperMock = $this->getMockBuilder(Data::class) ->disableOriginalConstructor() ->setMethods(['getMethodInstance']) ->getMock(); @@ -82,13 +79,11 @@ protected function setUp() $managerHelper = new ObjectManager($this); $this->block = $managerHelper->getObject(Form::class, [ 'paymentConfig' => $managerHelper->getObject(Config::class), - 'sessionQuote' => $this->sessionQuote, - 'gatewayConfig' => $this->gatewayConfig, - 'ccType' => $this->ccType, - 'storeManager' => $this->storeManager + 'sessionQuote' => $this->sessionQuoteMock, + 'gatewayConfig' => $this->gatewayConfigMock, + 'ccType' => $this->ccTypeMock, + 'paymentDataHelper' =>$this->paymentDataHelperMock, ]); - - $managerHelper->setBackwardCompatibleProperty($this->block, 'paymentDataHelper', $this->paymentDataHelper); } /** @@ -100,17 +95,18 @@ protected function setUp() */ public function testGetCcAvailableTypes($countryId, array $availableTypes, array $expected) { - $this->sessionQuote->expects(static::once()) + $this->sessionQuoteMock->expects(static::once()) ->method('getCountryId') ->willReturn($countryId); - $this->gatewayConfig->expects(static::once()) + $this->gatewayConfigMock->expects(static::once()) ->method('getAvailableCardTypes') + ->with($this->storeId) ->willReturn(self::$configCardTypes); - $this->gatewayConfig->expects(static::once()) + $this->gatewayConfigMock->expects(static::once()) ->method('getCountryAvailableCardTypes') - ->with($countryId) + ->with($countryId, $this->storeId) ->willReturn($availableTypes); $result = $this->block->getCcAvailableTypes(); @@ -127,7 +123,7 @@ public function countryCardTypesDataProvider() ['US', ['AE', 'VI'], ['American Express', 'Visa']], ['UK', ['VI'], ['Visa']], ['CA', ['MC'], ['MasterCard']], - ['UA', [], ['American Express', 'Visa', 'MasterCard', 'Discover', 'JBC']] + ['UA', [], ['American Express', 'Visa', 'MasterCard', 'Discover', 'JBC']], ]; } @@ -136,25 +132,15 @@ public function countryCardTypesDataProvider() */ public function testIsVaultEnabled() { - $storeId = 1; - $store = $this->getMockForAbstractClass(StoreInterface::class); - $this->storeManager->expects(static::once()) - ->method('getStore') - ->willReturn($store); - - $store->expects(static::once()) - ->method('getId') - ->willReturn($storeId); - $vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class); - $this->paymentDataHelper->expects(static::once()) + $this->paymentDataHelperMock->expects(static::once()) ->method('getMethodInstance') ->with(ConfigProvider::CC_VAULT_CODE) ->willReturn($vaultPayment); $vaultPayment->expects(static::once()) ->method('isActive') - ->with($storeId) + ->with($this->storeId) ->willReturn(true); static::assertTrue($this->block->isVaultEnabled()); @@ -165,12 +151,12 @@ public function testIsVaultEnabled() */ private function initCcTypeMock() { - $this->ccType = $this->getMockBuilder(CcType::class) + $this->ccTypeMock = $this->getMockBuilder(CcType::class) ->disableOriginalConstructor() ->setMethods(['getCcTypeLabelMap']) ->getMock(); - $this->ccType->expects(static::any()) + $this->ccTypeMock->expects(static::any()) ->method('getCcTypeLabelMap') ->willReturn(self::$baseCardTypes); } @@ -180,17 +166,20 @@ private function initCcTypeMock() */ private function initSessionQuoteMock() { - $this->sessionQuote = $this->getMockBuilder(Quote::class) + $this->sessionQuoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() - ->setMethods(['getQuote', 'getBillingAddress', 'getCountryId', '__wakeup']) + ->setMethods(['getQuote', 'getBillingAddress', 'getCountryId', '__wakeup', 'getStoreId']) ->getMock(); - $this->sessionQuote->expects(static::any()) + $this->sessionQuoteMock->expects(static::any()) ->method('getQuote') ->willReturnSelf(); - $this->sessionQuote->expects(static::any()) + $this->sessionQuoteMock->expects(static::any()) ->method('getBillingAddress') ->willReturnSelf(); + $this->sessionQuoteMock->expects(static::any()) + ->method('getStoreId') + ->willReturn($this->storeId); } /** @@ -198,7 +187,7 @@ private function initSessionQuoteMock() */ private function initGatewayConfigMock() { - $this->gatewayConfig = $this->getMockBuilder(GatewayConfig::class) + $this->gatewayConfigMock = $this->getMockBuilder(GatewayConfig::class) ->disableOriginalConstructor() ->setMethods(['getCountryAvailableCardTypes', 'getAvailableCardTypes']) ->getMock(); diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Adminhtml/Payment/GetClientTokenTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Adminhtml/Payment/GetClientTokenTest.php new file mode 100644 index 0000000000000..95ea2a07d4368 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Adminhtml/Payment/GetClientTokenTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Controller\Adminhtml\Payment; + +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\Session\Quote; +use Magento\Braintree\Controller\Adminhtml\Payment\GetClientToken; +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests \Magento\Braintree\Controller\Adminhtml\Payment\GetClientToken + */ +class GetClientTokenTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var GetClientToken + */ + private $action; + + /** + * @var Config|MockObject + */ + private $configMock; + + /** + * @var BraintreeAdapterFactory|MockObject + */ + private $adapterFactoryMock; + + /** + * @var Quote|MockObject + */ + private $quoteSessionMock; + + /** + * @var ResultFactory|MockObject + */ + private $resultFactoryMock; + + protected function setUp() + { + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $context = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->setMethods(['getResultFactory']) + ->getMock(); + $context->expects(static::any()) + ->method('getResultFactory') + ->willReturn($this->resultFactoryMock); + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getMerchantAccountId']) + ->getMock(); + $this->adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->quoteSessionMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->setMethods(['getStoreId']) + ->getMock(); + + $managerHelper = new ObjectManager($this); + $this->action = $managerHelper->getObject(GetClientToken::class, [ + 'context' => $context, + 'config' => $this->configMock, + 'adapterFactory' => $this->adapterFactoryMock, + 'quoteSession' => $this->quoteSessionMock, + ]); + } + + public function testExecute() + { + $storeId = '1'; + $clientToken = 'client_token'; + $responseMock = $this->getMockBuilder(ResultInterface::class) + ->setMethods(['setHttpResponseCode', 'renderResult', 'setHeader', 'setData']) + ->getMock(); + $responseMock->expects(static::once()) + ->method('setData') + ->with(['clientToken' => $clientToken]) + ->willReturn($responseMock); + $this->resultFactoryMock->expects(static::once()) + ->method('create') + ->willReturn($responseMock); + $this->quoteSessionMock->expects(static::once()) + ->method('getStoreId') + ->willReturn($storeId); + $this->configMock->expects(static::once()) + ->method('getMerchantAccountId') + ->with($storeId) + ->willReturn(null); + $adapterMock = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->setMethods(['generate']) + ->getMock(); + $adapterMock->expects(static::once()) + ->method('generate') + ->willReturn($clientToken); + $this->adapterFactoryMock->expects(static::once()) + ->method('create') + ->willReturn($adapterMock); + + $this->action->execute(); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Payment/GetNonceTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Payment/GetNonceTest.php index e78e54f011d44..4af63a9c87151 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Payment/GetNonceTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Payment/GetNonceTest.php @@ -15,6 +15,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Webapi\Exception; use Magento\Payment\Gateway\Command\ResultInterface as CommandResultInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; /** @@ -30,81 +31,84 @@ class GetNonceTest extends \PHPUnit\Framework\TestCase private $action; /** - * @var GetPaymentNonceCommand|\PHPUnit_Framework_MockObject_MockObject + * @var GetPaymentNonceCommand|MockObject */ - private $command; + private $commandMock; /** - * @var Session|\PHPUnit_Framework_MockObject_MockObject + * @var Session|MockObject */ - private $session; + private $sessionMock; /** - * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var LoggerInterface|MockObject */ - private $logger; + private $loggerMock; /** - * @var ResultFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ResultFactory|MockObject */ - private $resultFactory; + private $resultFactoryMock; /** - * @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterface|MockObject */ - private $result; + private $resultMock; /** - * @var Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|MockObject */ - private $request; + private $requestMock; /** - * @var CommandResultInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CommandResultInterface|MockObject */ - private $commandResult; + private $commandResultMock; protected function setUp() { $this->initResultFactoryMock(); - $this->request = $this->getMockBuilder(RequestInterface::class) + $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->setMethods(['getParam']) ->getMock(); - $this->command = $this->getMockBuilder(GetPaymentNonceCommand::class) + $this->commandMock = $this->getMockBuilder(GetPaymentNonceCommand::class) ->disableOriginalConstructor() ->setMethods(['execute', '__wakeup']) ->getMock(); - $this->commandResult = $this->getMockBuilder(CommandResultInterface::class) + $this->commandResultMock = $this->getMockBuilder(CommandResultInterface::class) ->setMethods(['get']) ->getMock(); - $this->session = $this->getMockBuilder(Session::class) + $this->sessionMock = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() - ->setMethods(['getCustomerId']) + ->setMethods(['getCustomerId', 'getStoreId']) ->getMock(); + $this->sessionMock->expects(static::once()) + ->method('getStoreId') + ->willReturn(null); - $this->logger = $this->createMock(LoggerInterface::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); $context = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); $context->expects(static::any()) ->method('getRequest') - ->willReturn($this->request); + ->willReturn($this->requestMock); $context->expects(static::any()) ->method('getResultFactory') - ->willReturn($this->resultFactory); + ->willReturn($this->resultFactoryMock); $managerHelper = new ObjectManager($this); $this->action = $managerHelper->getObject(GetNonce::class, [ 'context' => $context, - 'logger' => $this->logger, - 'session' => $this->session, - 'command' => $this->command + 'logger' => $this->loggerMock, + 'session' => $this->sessionMock, + 'command' => $this->commandMock, ]); } @@ -113,28 +117,28 @@ protected function setUp() */ public function testExecuteWithException() { - $this->request->expects(static::once()) + $this->requestMock->expects(static::once()) ->method('getParam') ->with('public_hash') ->willReturn(null); - $this->session->expects(static::once()) + $this->sessionMock->expects(static::once()) ->method('getCustomerId') ->willReturn(null); $exception = new \Exception('The "publicHash" field does not exists'); - $this->command->expects(static::once()) + $this->commandMock->expects(static::once()) ->method('execute') ->willThrowException($exception); - $this->logger->expects(static::once()) + $this->loggerMock->expects(static::once()) ->method('critical') ->with($exception); - $this->result->expects(static::once()) + $this->resultMock->expects(static::once()) ->method('setHttpResponseCode') ->with(Exception::HTTP_BAD_REQUEST); - $this->result->expects(static::once()) + $this->resultMock->expects(static::once()) ->method('setData') ->with(['message' => 'Sorry, but something went wrong']); @@ -150,32 +154,32 @@ public function testExecute() $publicHash = '65b7bae0dcb690d93'; $nonce = 'f1hc45'; - $this->request->expects(static::once()) + $this->requestMock->expects(static::once()) ->method('getParam') ->with('public_hash') ->willReturn($publicHash); - $this->session->expects(static::once()) + $this->sessionMock->expects(static::once()) ->method('getCustomerId') ->willReturn($customerId); - $this->commandResult->expects(static::once()) + $this->commandResultMock->expects(static::once()) ->method('get') ->willReturn([ 'paymentMethodNonce' => $nonce ]); - $this->command->expects(static::once()) + $this->commandMock->expects(static::once()) ->method('execute') - ->willReturn($this->commandResult); + ->willReturn($this->commandResultMock); - $this->result->expects(static::once()) + $this->resultMock->expects(static::once()) ->method('setData') ->with(['paymentMethodNonce' => $nonce]); - $this->logger->expects(static::never()) + $this->loggerMock->expects(static::never()) ->method('critical'); - $this->result->expects(static::never()) + $this->resultMock->expects(static::never()) ->method('setHttpResponseCode'); $this->action->execute(); @@ -186,17 +190,17 @@ public function testExecute() */ private function initResultFactoryMock() { - $this->result = $this->getMockBuilder(ResultInterface::class) + $this->resultMock = $this->getMockBuilder(ResultInterface::class) ->setMethods(['setHttpResponseCode', 'renderResult', 'setHeader', 'setData']) ->getMock(); - $this->resultFactory = $this->getMockBuilder(ResultFactory::class) + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->resultFactory->expects(static::once()) + $this->resultFactoryMock->expects(static::once()) ->method('create') - ->willReturn($this->result); + ->willReturn($this->resultMock); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php index 5a10b4abb3fbc..4bea03153b93b 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Test\Unit\Controller\Paypal; use Magento\Braintree\Controller\Paypal\PlaceOrder; @@ -186,7 +187,7 @@ public function testExecuteException() ->method('addExceptionMessage') ->with( self::isInstanceOf('\InvalidArgumentException'), - 'We can\'t initialize checkout.' + 'Checkout failed to initialize. Verify and try again.' ); self::assertEquals($this->placeOrder->execute(), $resultMock); diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php index cb911a8396b36..609b7f21dbf87 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Test\Unit\Controller\Paypal; use Magento\Quote\Model\Quote; @@ -188,7 +189,7 @@ public function testExecuteException() ->method('addExceptionMessage') ->with( self::isInstanceOf('\InvalidArgumentException'), - 'We can\'t initialize checkout.' + 'Checkout failed to initialize. Verify and try again.' ); $this->resultFactoryMock->expects(self::once()) @@ -235,7 +236,7 @@ public function testExecuteExceptionPaymentWithoutNonce() ->method('addExceptionMessage') ->with( self::isInstanceOf(\Magento\Framework\Exception\LocalizedException::class), - 'We can\'t initialize checkout.' + 'Checkout failed to initialize. Verify and try again.' ); $this->resultFactoryMock->expects(self::once()) diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/SaveShippingMethodTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/SaveShippingMethodTest.php index 5be5df0e33c49..32ed698189fa7 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/SaveShippingMethodTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/SaveShippingMethodTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Test\Unit\Controller\Paypal; use Magento\Quote\Model\Quote; @@ -225,7 +226,10 @@ public function testExecuteAjaxException() $this->messageManagerMock->expects(self::once()) ->method('addExceptionMessage') - ->with(self::isInstanceOf('\InvalidArgumentException'), 'We can\'t initialize checkout.'); + ->with( + self::isInstanceOf('\InvalidArgumentException'), + 'Checkout failed to initialize. Verify and try again.' + ); $this->urlMock->expects(self::once()) ->method('getUrl') @@ -265,7 +269,10 @@ public function testExecuteException() $this->messageManagerMock->expects(self::once()) ->method('addExceptionMessage') - ->with(self::isInstanceOf('\InvalidArgumentException'), 'We can\'t initialize checkout.'); + ->with( + self::isInstanceOf('\InvalidArgumentException'), + 'Checkout failed to initialize. Verify and try again.' + ); $this->urlMock->expects(self::once()) ->method('getUrl') diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php index 56ea1f97fa165..845a02930d709 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php @@ -6,26 +6,25 @@ namespace Magento\Braintree\Test\Unit\Gateway\Command; use Braintree\IsNode; -use Braintree\MultipleValueNode; -use Braintree\TextNode; use Magento\Braintree\Gateway\Command\CaptureStrategyCommand; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Braintree\Model\Adapter\BraintreeSearchAdapter; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\SearchCriteria; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Payment\Gateway\Command; use Magento\Payment\Gateway\Command\CommandPoolInterface; use Magento\Payment\Gateway\Command\GatewayCommand; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\Order\Payment; -use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\ResourceModel\Order\Payment\Transaction\CollectionFactory; -use Magento\Braintree\Model\Adapter\BraintreeAdapter; -use Magento\Braintree\Model\Adapter\BraintreeSearchAdapter; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class CaptureStrategyCommandTest + * Tests \Magento\Braintree\Gateway\Command\CaptureStrategyCommand. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -37,44 +36,44 @@ class CaptureStrategyCommandTest extends \PHPUnit\Framework\TestCase private $strategyCommand; /** - * @var CommandPoolInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CommandPoolInterface|MockObject */ - private $commandPool; + private $commandPoolMock; /** - * @var TransactionRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var TransactionRepositoryInterface|MockObject */ - private $transactionRepository; + private $transactionRepositoryMock; /** - * @var FilterBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var FilterBuilder|MockObject */ - private $filterBuilder; + private $filterBuilderMock; /** - * @var SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var SearchCriteriaBuilder|MockObject */ - private $searchCriteriaBuilder; + private $searchCriteriaBuilderMock; /** - * @var Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ - private $payment; + private $paymentMock; /** - * @var GatewayCommand|\PHPUnit_Framework_MockObject_MockObject + * @var GatewayCommand|MockObject */ - private $command; + private $commandMock; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ private $subjectReaderMock; /** - * @var BraintreeAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapter|MockObject */ - private $braintreeAdapter; + private $braintreeAdapterMock; /** * @var BraintreeSearchAdapter @@ -83,7 +82,7 @@ class CaptureStrategyCommandTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->commandPool = $this->getMockBuilder(CommandPoolInterface::class) + $this->commandPoolMock = $this->getMockBuilder(CommandPoolInterface::class) ->disableOriginalConstructor() ->setMethods(['get', '__wakeup']) ->getMock(); @@ -97,18 +96,26 @@ protected function setUp() $this->initFilterBuilderMock(); $this->initSearchCriteriaBuilderMock(); - $this->braintreeAdapter = $this->getMockBuilder(BraintreeAdapter::class) + $this->braintreeAdapterMock = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) ->disableOriginalConstructor() ->getMock(); + $adapterFactoryMock->expects(self::any()) + ->method('create') + ->willReturn($this->braintreeAdapterMock); + $this->braintreeSearchAdapter = new BraintreeSearchAdapter(); $this->strategyCommand = new CaptureStrategyCommand( - $this->commandPool, - $this->transactionRepository, - $this->filterBuilder, - $this->searchCriteriaBuilder, + $this->commandPoolMock, + $this->transactionRepositoryMock, + $this->filterBuilderMock, + $this->searchCriteriaBuilderMock, $this->subjectReaderMock, - $this->braintreeAdapter, + $adapterFactoryMock, $this->braintreeSearchAdapter ); } @@ -126,24 +133,24 @@ public function testSaleExecute() ->with($subject) ->willReturn($paymentData); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getAuthorizationTransaction') ->willReturn(false); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getId') ->willReturn(1); $this->buildSearchCriteria(); - $this->transactionRepository->expects(static::once()) + $this->transactionRepositoryMock->expects(static::once()) ->method('getTotalCount') ->willReturn(0); - $this->commandPool->expects(static::once()) + $this->commandPoolMock->expects(static::once()) ->method('get') ->with(CaptureStrategyCommand::SALE) - ->willReturn($this->command); + ->willReturn($this->commandMock); $this->strategyCommand->execute($subject); } @@ -162,20 +169,20 @@ public function testCaptureExecute() ->with($subject) ->willReturn($paymentData); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getAuthorizationTransaction') ->willReturn(true); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getLastTransId') ->willReturn($lastTransId); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getId') ->willReturn(1); $this->buildSearchCriteria(); - $this->transactionRepository->expects(static::once()) + $this->transactionRepositoryMock->expects(static::once()) ->method('getTotalCount') ->willReturn(0); @@ -185,17 +192,17 @@ public function testCaptureExecute() ->method('maximumCount') ->willReturn(0); - $this->commandPool->expects(static::once()) + $this->commandPoolMock->expects(static::once()) ->method('get') ->with(CaptureStrategyCommand::CAPTURE) - ->willReturn($this->command); + ->willReturn($this->commandMock); $this->strategyCommand->execute($subject); } /** * @param string $lastTransactionId - * @return \Braintree\ResourceCollection|\PHPUnit_Framework_MockObject_MockObject + * @return \Braintree\ResourceCollection|MockObject */ private function getNotExpiredExpectedCollection($lastTransactionId) { @@ -208,7 +215,7 @@ private function getNotExpiredExpectedCollection($lastTransactionId) ->disableOriginalConstructor() ->getMock(); - $this->braintreeAdapter->expects(static::once()) + $this->braintreeAdapterMock->expects(static::once()) ->method('search') ->with( static::callback( @@ -247,20 +254,20 @@ public function testExpiredAuthorizationPerformVaultCaptureExecute() ->with($subject) ->willReturn($paymentData); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getAuthorizationTransaction') ->willReturn(true); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getLastTransId') ->willReturn($lastTransId); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getId') ->willReturn(1); $this->buildSearchCriteria(); - $this->transactionRepository->expects(static::once()) + $this->transactionRepositoryMock->expects(static::once()) ->method('getTotalCount') ->willReturn(0); @@ -270,10 +277,10 @@ public function testExpiredAuthorizationPerformVaultCaptureExecute() ->method('maximumCount') ->willReturn(1); - $this->commandPool->expects(static::once()) + $this->commandPoolMock->expects(static::once()) ->method('get') ->with(CaptureStrategyCommand::VAULT_CAPTURE) - ->willReturn($this->command); + ->willReturn($this->commandMock); $this->strategyCommand->execute($subject); } @@ -291,97 +298,104 @@ public function testVaultCaptureExecute() ->with($subject) ->willReturn($paymentData); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getAuthorizationTransaction') ->willReturn(true); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('getId') ->willReturn(1); $this->buildSearchCriteria(); - $this->transactionRepository->expects(static::once()) + $this->transactionRepositoryMock->expects(static::once()) ->method('getTotalCount') ->willReturn(1); - $this->commandPool->expects(static::once()) + $this->commandPoolMock->expects(static::once()) ->method('get') ->with(CaptureStrategyCommand::VAULT_CAPTURE) - ->willReturn($this->command); + ->willReturn($this->commandMock); $this->strategyCommand->execute($subject); } /** - * Create mock for payment data object and order payment - * @return \PHPUnit_Framework_MockObject_MockObject + * Creates mock for payment data object and order payment + * @return MockObject */ private function getPaymentDataObjectMock() { - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->getMock(); $mock = $this->getMockBuilder(PaymentDataObject::class) - ->setMethods(['getPayment']) + ->setMethods(['getPayment', 'getOrder']) ->disableOriginalConstructor() ->getMock(); $mock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); + + $orderMock = $this->getMockBuilder(OrderAdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $mock->method('getOrder') + ->willReturn($orderMock); return $mock; } /** - * Create mock for gateway command object + * Creates mock for gateway command object */ private function initCommandMock() { - $this->command = $this->getMockBuilder(GatewayCommand::class) + $this->commandMock = $this->getMockBuilder(GatewayCommand::class) ->disableOriginalConstructor() ->setMethods(['execute']) ->getMock(); - $this->command->expects(static::once()) + $this->commandMock->expects(static::once()) ->method('execute') ->willReturn([]); } /** - * Create mock for filter object + * Creates mock for filter object */ private function initFilterBuilderMock() { - $this->filterBuilder = $this->getMockBuilder(FilterBuilder::class) + $this->filterBuilderMock = $this->getMockBuilder(FilterBuilder::class) ->disableOriginalConstructor() ->setMethods(['setField', 'setValue', 'create', '__wakeup']) ->getMock(); } /** - * Build search criteria + * Builds search criteria */ private function buildSearchCriteria() { - $this->filterBuilder->expects(static::exactly(2)) + $this->filterBuilderMock->expects(static::exactly(2)) ->method('setField') ->willReturnSelf(); - $this->filterBuilder->expects(static::exactly(2)) + $this->filterBuilderMock->expects(static::exactly(2)) ->method('setValue') ->willReturnSelf(); $searchCriteria = new SearchCriteria(); - $this->searchCriteriaBuilder->expects(static::exactly(2)) + $this->searchCriteriaBuilderMock->expects(static::exactly(2)) ->method('addFilters') ->willReturnSelf(); - $this->searchCriteriaBuilder->expects(static::once()) + $this->searchCriteriaBuilderMock->expects(static::once()) ->method('create') ->willReturn($searchCriteria); - $this->transactionRepository->expects(static::once()) + $this->transactionRepositoryMock->expects(static::once()) ->method('getList') ->with($searchCriteria) ->willReturnSelf(); @@ -392,7 +406,7 @@ private function buildSearchCriteria() */ private function initSearchCriteriaBuilderMock() { - $this->searchCriteriaBuilder = $this->getMockBuilder(SearchCriteriaBuilder::class) + $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) ->disableOriginalConstructor() ->setMethods(['addFilters', 'create', '__wakeup']) ->getMock(); @@ -403,7 +417,7 @@ private function initSearchCriteriaBuilderMock() */ private function initTransactionRepositoryMock() { - $this->transactionRepository = $this->getMockBuilder(TransactionRepositoryInterface::class) + $this->transactionRepositoryMock = $this->getMockBuilder(TransactionRepositoryInterface::class) ->disableOriginalConstructor() ->setMethods(['getList', 'getTotalCount', 'delete', 'get', 'save', 'create', '__wakeup']) ->getMock(); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php index 333f29eb29136..23167af02a97b 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php @@ -6,15 +6,16 @@ namespace Magento\Braintree\Test\Unit\Gateway\Command; use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator; use Magento\Braintree\Model\Adapter\BraintreeAdapter; -use Magento\Payment\Gateway\Command; -use Magento\Payment\Gateway\Command\Result\ArrayResultFactory; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Payment\Gateway\Command\Result\ArrayResult; +use Magento\Payment\Gateway\Command\Result\ArrayResultFactory; use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Vault\Model\PaymentToken; use Magento\Vault\Model\PaymentTokenManagement; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class GetPaymentNonceCommandTest @@ -29,82 +30,89 @@ class GetPaymentNonceCommandTest extends \PHPUnit\Framework\TestCase private $command; /** - * @var BraintreeAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapter|MockObject */ - private $adapter; + private $adapterMock; /** - * @var PaymentTokenManagement|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentTokenManagement|MockObject */ - private $tokenManagement; + private $tokenManagementMock; /** - * @var PaymentToken|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentToken|MockObject */ - private $paymentToken; + private $paymentTokenMock; /** - * @var ArrayResultFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ArrayResultFactory|MockObject */ - private $resultFactory; + private $resultFactoryMock; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; /** - * @var PaymentNonceResponseValidator|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentNonceResponseValidator|MockObject */ - private $responseValidator; + private $responseValidatorMock; /** - * @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterface|MockObject */ - private $validationResult; + private $validationResultMock; protected function setUp() { - $this->paymentToken = $this->getMockBuilder(PaymentToken::class) + $this->paymentTokenMock = $this->getMockBuilder(PaymentToken::class) ->disableOriginalConstructor() ->setMethods(['getGatewayToken']) ->getMock(); - $this->tokenManagement = $this->getMockBuilder(PaymentTokenManagement::class) + $this->tokenManagementMock = $this->getMockBuilder(PaymentTokenManagement::class) ->disableOriginalConstructor() ->setMethods(['getByPublicHash']) ->getMock(); - $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + $this->adapterMock = $this->getMockBuilder(BraintreeAdapter::class) ->disableOriginalConstructor() ->setMethods(['createNonce']) ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactoryMock->expects(self::any()) + ->method('create') + ->willReturn($this->adapterMock); - $this->resultFactory = $this->getMockBuilder(ArrayResultFactory::class) + $this->resultFactoryMock = $this->getMockBuilder(ArrayResultFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->setMethods(['readPublicHash', 'readCustomerId']) ->getMock(); - $this->validationResult = $this->getMockBuilder(ResultInterface::class) - ->setMethods(['isValid', 'getFailsDescription']) + $this->validationResultMock = $this->getMockBuilder(ResultInterface::class) + ->setMethods(['isValid', 'getFailsDescription', 'getErrorCodes']) ->getMock(); - $this->responseValidator = $this->getMockBuilder(PaymentNonceResponseValidator::class) + $this->responseValidatorMock = $this->getMockBuilder(PaymentNonceResponseValidator::class) ->disableOriginalConstructor() ->setMethods(['validate', 'isValid', 'getFailsDescription']) ->getMock(); $this->command = new GetPaymentNonceCommand( - $this->tokenManagement, - $this->adapter, - $this->resultFactory, - $this->subjectReader, - $this->responseValidator + $this->tokenManagementMock, + $adapterFactoryMock, + $this->resultFactoryMock, + $this->subjectReaderMock, + $this->responseValidatorMock ); } @@ -117,11 +125,11 @@ public function testExecuteWithExceptionForPublicHash() { $exception = new \InvalidArgumentException('The "publicHash" field does not exists'); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPublicHash') ->willThrowException($exception); - $this->subjectReader->expects(static::never()) + $this->subjectReaderMock->expects(self::never()) ->method('readCustomerId'); $this->command->execute([]); @@ -136,16 +144,16 @@ public function testExecuteWithExceptionForCustomerId() { $publicHash = '3wv2m24d2er3'; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPublicHash') ->willReturn($publicHash); $exception = new \InvalidArgumentException('The "customerId" field does not exists'); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readCustomerId') ->willThrowException($exception); - $this->tokenManagement->expects(static::never()) + $this->tokenManagementMock->expects(static::never()) ->method('getByPublicHash'); $this->command->execute(['publicHash' => $publicHash]); @@ -161,20 +169,20 @@ public function testExecuteWithExceptionForTokenManagement() $publicHash = '3wv2m24d2er3'; $customerId = 1; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPublicHash') ->willReturn($publicHash); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readCustomerId') ->willReturn($customerId); $exception = new \Exception('No available payment tokens'); - $this->tokenManagement->expects(static::once()) + $this->tokenManagementMock->expects(static::once()) ->method('getByPublicHash') ->willThrowException($exception); - $this->paymentToken->expects(static::never()) + $this->paymentTokenMock->expects(self::never()) ->method('getGatewayToken'); $this->command->execute(['publicHash' => $publicHash, 'customerId' => $customerId]); @@ -191,44 +199,44 @@ public function testExecuteWithFailedValidation() $customerId = 1; $token = 'jd2vnq'; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPublicHash') ->willReturn($publicHash); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readCustomerId') ->willReturn($customerId); - $this->tokenManagement->expects(static::once()) + $this->tokenManagementMock->expects(static::once()) ->method('getByPublicHash') ->with($publicHash, $customerId) - ->willReturn($this->paymentToken); + ->willReturn($this->paymentTokenMock); - $this->paymentToken->expects(static::once()) + $this->paymentTokenMock->expects(static::once()) ->method('getGatewayToken') ->willReturn($token); $obj = new \stdClass(); $obj->success = false; - $this->adapter->expects(static::once()) + $this->adapterMock->expects(static::once()) ->method('createNonce') ->with($token) ->willReturn($obj); - $this->responseValidator->expects(static::once()) + $this->responseValidatorMock->expects(static::once()) ->method('validate') ->with(['response' => ['object' => $obj]]) - ->willReturn($this->validationResult); + ->willReturn($this->validationResultMock); - $this->validationResult->expects(static::once()) + $this->validationResultMock->expects(static::once()) ->method('isValid') ->willReturn(false); - $this->validationResult->expects(static::once()) + $this->validationResultMock->expects(static::once()) ->method('getFailsDescription') ->willReturn(['Payment method nonce can\'t be retrieved.']); - $this->resultFactory->expects(static::never()) + $this->resultFactoryMock->expects(static::never()) ->method('create'); $this->command->execute(['publicHash' => $publicHash, 'customerId' => $customerId]); @@ -244,20 +252,20 @@ public function testExecute() $token = 'jd2vnq'; $nonce = 's1dj23'; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPublicHash') ->willReturn($publicHash); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readCustomerId') ->willReturn($customerId); - $this->tokenManagement->expects(static::once()) + $this->tokenManagementMock->expects(static::once()) ->method('getByPublicHash') ->with($publicHash, $customerId) - ->willReturn($this->paymentToken); + ->willReturn($this->paymentTokenMock); - $this->paymentToken->expects(static::once()) + $this->paymentTokenMock->expects(static::once()) ->method('getGatewayToken') ->willReturn($token); @@ -265,21 +273,21 @@ public function testExecute() $obj->success = true; $obj->paymentMethodNonce = new \stdClass(); $obj->paymentMethodNonce->nonce = $nonce; - $this->adapter->expects(static::once()) + $this->adapterMock->expects(static::once()) ->method('createNonce') ->with($token) ->willReturn($obj); - $this->responseValidator->expects(static::once()) + $this->responseValidatorMock->expects(static::once()) ->method('validate') ->with(['response' => ['object' => $obj]]) - ->willReturn($this->validationResult); + ->willReturn($this->validationResultMock); - $this->validationResult->expects(static::once()) + $this->validationResultMock->expects(static::once()) ->method('isValid') ->willReturn(true); - $this->validationResult->expects(static::never()) + $this->validationResultMock->expects(self::never()) ->method('getFailsDescription'); $expected = $this->getMockBuilder(ArrayResult::class) @@ -289,12 +297,12 @@ public function testExecute() $expected->expects(static::once()) ->method('get') ->willReturn(['paymentMethodNonce' => $nonce]); - $this->resultFactory->expects(static::once()) + $this->resultFactoryMock->expects(static::once()) ->method('create') ->willReturn($expected); $actual = $this->command->execute(['publicHash' => $publicHash, 'customerId' => $customerId]); - static::assertEquals($expected, $actual); - static::assertEquals($nonce, $actual->get()['paymentMethodNonce']); + self::assertEquals($expected, $actual); + self::assertEquals($nonce, $actual->get()['paymentMethodNonce']); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/CanVoidHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/CanVoidHandlerTest.php index 793700ab1971f..031e53690451f 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/CanVoidHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/CanVoidHandlerTest.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Test\Unit\Gateway\Config; use Magento\Braintree\Gateway\Config\CanVoidHandler; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Model\InfoInterface; use Magento\Sales\Model\Order\Payment; @@ -39,7 +39,7 @@ public function testHandleNotOrderPayment() static::assertFalse($voidHandler->handle($subject)); } - public function testHandleSomeAmoutWasPaid() + public function testHandleSomeAmountWasPaid() { $paymentDO = $this->createMock(PaymentDataObjectInterface::class); $subject = [ diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php index 7b9d59a5bc482..36ea3aea465dd 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php @@ -142,7 +142,7 @@ public function testGetCcTypesMapper($value, $expected) static::assertEquals( $expected, - $this->model->getCctypesMapper() + $this->model->getCcTypesMapper() ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php deleted file mode 100644 index b2207563b8b0f..0000000000000 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Helper/SubjectReaderTest.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Braintree\Test\Unit\Gateway\Helper; - -use Braintree\Transaction; -use InvalidArgumentException; -use Magento\Braintree\Gateway\Helper\SubjectReader; - -/** - * Class SubjectReaderTest - */ -class SubjectReaderTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var SubjectReader - */ - private $subjectReader; - - protected function setUp() - { - $this->subjectReader = new SubjectReader(); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readCustomerId - * @expectedException InvalidArgumentException - * @expectedExceptionMessage The "customerId" field does not exists - */ - public function testReadCustomerIdWithException() - { - $this->subjectReader->readCustomerId([]); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readCustomerId - */ - public function testReadCustomerId() - { - $customerId = 1; - static::assertEquals($customerId, $this->subjectReader->readCustomerId(['customer_id' => $customerId])); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readPublicHash - * @expectedException InvalidArgumentException - * @expectedExceptionMessage The "public_hash" field does not exists - */ - public function testReadPublicHashWithException() - { - $this->subjectReader->readPublicHash([]); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readPublicHash - */ - public function testReadPublicHash() - { - $hash = 'fj23djf2o1fd'; - static::assertEquals($hash, $this->subjectReader->readPublicHash(['public_hash' => $hash])); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readPayPal - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Transaction has't paypal attribute - */ - public function testReadPayPalWithException() - { - $transaction = Transaction::factory([ - 'id' => 'u38rf8kg6vn' - ]); - $this->subjectReader->readPayPal($transaction); - } - - /** - * @covers \Magento\Braintree\Gateway\Helper\SubjectReader::readPayPal - */ - public function testReadPayPal() - { - $paypal = [ - 'paymentId' => '3ek7dk7fn0vi1', - 'payerEmail' => 'payer@example.com' - ]; - $transaction = Transaction::factory([ - 'id' => '4yr95vb', - 'paypal' => $paypal - ]); - - static::assertEquals($paypal, $this->subjectReader->readPayPal($transaction)); - } -} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php new file mode 100644 index 0000000000000..c871dc69a5370 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionRefundTest.php @@ -0,0 +1,155 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Gateway\Http\Client; + +use Magento\Braintree\Gateway\Http\Client\TransactionRefund; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Payment\Gateway\Http\TransferInterface; +use Magento\Payment\Model\Method\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Psr\Log\LoggerInterface; +use Magento\Braintree\Gateway\Request\PaymentDataBuilder; + +/** + * Tests \Magento\Braintree\Gateway\Http\Client\TransactionRefund. + */ +class TransactionRefundTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var TransactionRefund + */ + private $client; + + /** + * @var Logger|MockObject + */ + private $loggerMock; + + /** + * @var BraintreeAdapter|MockObject + */ + private $adapterMock; + + /** + * @var string + */ + private $transactionId = 'px4kpev5'; + + /** + * @var string + */ + private $paymentAmount = '100.00'; + + /** + * @inheritdoc + */ + protected function setUp() + { + /** @var LoggerInterface|MockObject $criticalLoggerMock */ + $criticalLoggerMock = $this->getMockForAbstractClass(LoggerInterface::class); + $this->loggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapterMock = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactoryMock->expects(self::once()) + ->method('create') + ->willReturn($this->adapterMock); + + $this->client = new TransactionRefund($criticalLoggerMock, $this->loggerMock, $adapterFactoryMock); + } + + /** + * @return void + * + * @expectedException \Magento\Payment\Gateway\Http\ClientException + * @expectedExceptionMessage Test messages + */ + public function testPlaceRequestException() + { + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionRefund::class, + 'response' => [], + ] + ); + + $this->adapterMock->expects($this->once()) + ->method('refund') + ->with($this->transactionId, $this->paymentAmount) + ->willThrowException(new \Exception('Test messages')); + + /** @var TransferInterface|MockObject $transferObjectMock */ + $transferObjectMock = $this->getTransferObjectMock(); + + $this->client->placeRequest($transferObjectMock); + } + + /** + * @return void + */ + public function testPlaceRequestSuccess() + { + $response = new \stdClass; + $response->success = true; + $this->adapterMock->expects($this->once()) + ->method('refund') + ->with($this->transactionId, $this->paymentAmount) + ->willReturn($response); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionRefund::class, + 'response' => ['success' => 1], + ] + ); + + $actualResult = $this->client->placeRequest($this->getTransferObjectMock()); + + $this->assertTrue(is_object($actualResult['object'])); + $this->assertEquals(['object' => $response], $actualResult); + } + + /** + * Creates mock object for TransferInterface. + * + * @return TransferInterface|MockObject + */ + private function getTransferObjectMock() + { + $transferObjectMock = $this->createMock(TransferInterface::class); + $transferObjectMock->expects($this->once()) + ->method('getBody') + ->willReturn($this->getTransferData()); + + return $transferObjectMock; + } + + /** + * Creates stub request data. + * + * @return array + */ + private function getTransferData() + { + return [ + 'transaction_id' => $this->transactionId, + PaymentDataBuilder::AMOUNT => $this->paymentAmount, + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php index 0837ecaea7a13..1317deeddb7fe 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php @@ -7,12 +7,14 @@ use Magento\Braintree\Gateway\Http\Client\TransactionSale; use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Payment\Gateway\Http\TransferInterface; use Magento\Payment\Model\Method\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; /** - * Class TransactionSaleTest + * Tests \Magento\Braintree\Gateway\Http\Client\TransactionSale. */ class TransactionSaleTest extends \PHPUnit\Framework\TestCase { @@ -22,35 +24,41 @@ class TransactionSaleTest extends \PHPUnit\Framework\TestCase private $model; /** - * @var Logger|\PHPUnit_Framework_MockObject_MockObject + * @var Logger|MockObject */ private $loggerMock; /** - * @var BraintreeAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapter|MockObject */ - private $adapter; + private $adapterMock; /** - * Set up - * - * @return void + * @inheritdoc */ protected function setUp() { + /** @var LoggerInterface|MockObject $criticalLoggerMock */ $criticalLoggerMock = $this->getMockForAbstractClass(LoggerInterface::class); $this->loggerMock = $this->getMockBuilder(Logger::class) ->disableOriginalConstructor() ->getMock(); - $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + $this->adapterMock = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) ->disableOriginalConstructor() ->getMock(); + $adapterFactoryMock->expects(self::once()) + ->method('create') + ->willReturn($this->adapterMock); - $this->model = new TransactionSale($criticalLoggerMock, $this->loggerMock, $this->adapter); + $this->model = new TransactionSale($criticalLoggerMock, $this->loggerMock, $adapterFactoryMock); } /** - * Run test placeRequest method (exception) + * Runs test placeRequest method (exception) * * @return void * @@ -69,11 +77,11 @@ public function testPlaceRequestException() ] ); - $this->adapter->expects($this->once()) + $this->adapterMock->expects($this->once()) ->method('sale') ->willThrowException(new \Exception('Test messages')); - /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */ + /** @var TransferInterface|MockObject $transferObjectMock */ $transferObjectMock = $this->getTransferObjectMock(); $this->model->placeRequest($transferObjectMock); @@ -87,7 +95,7 @@ public function testPlaceRequestException() public function testPlaceRequestSuccess() { $response = $this->getResponseObject(); - $this->adapter->expects($this->once()) + $this->adapterMock->expects($this->once()) ->method('sale') ->with($this->getTransferData()) ->willReturn($response); @@ -109,7 +117,9 @@ public function testPlaceRequestSuccess() } /** - * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject + * Creates mock object for TransferInterface. + * + * @return TransferInterface|MockObject */ private function getTransferObjectMock() { @@ -122,6 +132,8 @@ private function getTransferObjectMock() } /** + * Creates stub for a response. + * * @return \stdClass */ private function getResponseObject() @@ -133,6 +145,8 @@ private function getResponseObject() } /** + * Creates stub request data. + * * @return array */ private function getTransferData() diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSubmitForSettlementTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSubmitForSettlementTest.php index 86113c34ba218..2e77824817942 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSubmitForSettlementTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionSubmitForSettlementTest.php @@ -8,12 +8,14 @@ use Braintree\Result\Successful; use Magento\Braintree\Gateway\Http\Client\TransactionSubmitForSettlement; use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Payment\Gateway\Http\TransferInterface; use Magento\Payment\Model\Method\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; /** - * Class TransactionSubmitForSettlementTest + * Tests \Magento\Braintree\Gateway\Http\Client\TransactionSubmitForSettlement. */ class TransactionSubmitForSettlementTest extends \PHPUnit\Framework\TestCase { @@ -23,31 +25,39 @@ class TransactionSubmitForSettlementTest extends \PHPUnit\Framework\TestCase private $client; /** - * @var Logger|\PHPUnit_Framework_MockObject_MockObject + * @var Logger|MockObject */ - private $logger; + private $loggerMock; /** - * @var BraintreeAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapter|MockObject */ - private $adapter; + private $adapterMock; protected function setUp() { + /** @var LoggerInterface|MockObject $criticalLoggerMock */ $criticalLoggerMock = $this->getMockForAbstractClass(LoggerInterface::class); - $this->logger = $this->getMockBuilder(Logger::class) + $this->loggerMock = $this->getMockBuilder(Logger::class) ->disableOriginalConstructor() ->setMethods(['debug']) ->getMock(); - $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + + $this->adapterMock = $this->getMockBuilder(BraintreeAdapter::class) ->disableOriginalConstructor() ->setMethods(['submitForSettlement']) ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactoryMock->method('create') + ->willReturn($this->adapterMock); $this->client = new TransactionSubmitForSettlement( $criticalLoggerMock, - $this->logger, - $this->adapter + $this->loggerMock, + $adapterFactoryMock ); } @@ -59,13 +69,13 @@ protected function setUp() public function testPlaceRequestWithException() { $exception = new \Exception('Transaction has been declined'); - $this->adapter->expects(static::once()) + $this->adapterMock->expects(static::once()) ->method('submitForSettlement') ->willThrowException($exception); - /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */ - $transferObjectMock = $this->getTransferObjectMock(); - $this->client->placeRequest($transferObjectMock); + /** @var TransferInterface|MockObject $transferObject */ + $transferObject = $this->getTransferObjectMock(); + $this->client->placeRequest($transferObject); } /** @@ -74,19 +84,21 @@ public function testPlaceRequestWithException() public function testPlaceRequest() { $data = new Successful(['success'], [true]); - $this->adapter->expects(static::once()) + $this->adapterMock->expects(static::once()) ->method('submitForSettlement') ->willReturn($data); - /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */ - $transferObjectMock = $this->getTransferObjectMock(); - $response = $this->client->placeRequest($transferObjectMock); + /** @var TransferInterface|MockObject $transferObject */ + $transferObject = $this->getTransferObjectMock(); + $response = $this->client->placeRequest($transferObject); static::assertTrue(is_object($response['object'])); static::assertEquals(['object' => $data], $response); } /** - * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject + * Creates mock for TransferInterface + * + * @return TransferInterface|MockObject */ private function getTransferObjectMock() { @@ -95,7 +107,7 @@ private function getTransferObjectMock() ->method('getBody') ->willReturn([ 'transaction_id' => 'vb4c6b', - 'amount' => 124.00 + 'amount' => 124.00, ]); return $mock; diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionVoidTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionVoidTest.php new file mode 100644 index 0000000000000..17f63d0659b93 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Http/Client/TransactionVoidTest.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Gateway\Http\Client; + +use Magento\Braintree\Gateway\Http\Client\TransactionVoid; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Payment\Gateway\Http\TransferInterface; +use Magento\Payment\Model\Method\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Psr\Log\LoggerInterface; + +/** + * Tests \Magento\Braintree\Gateway\Http\Client\TransactionVoid. + */ +class TransactionVoidTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var TransactionVoid + */ + private $client; + + /** + * @var Logger|MockObject + */ + private $loggerMock; + + /** + * @var BraintreeAdapter|MockObject + */ + private $adapterMock; + + /** + * @var string + */ + private $transactionId = 'px4kpev5'; + + /** + * @inheritdoc + */ + protected function setUp() + { + /** @var LoggerInterface|MockObject $criticalLoggerMock */ + $criticalLoggerMock = $this->getMockForAbstractClass(LoggerInterface::class); + $this->loggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapterMock = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactoryMock->expects(self::once()) + ->method('create') + ->willReturn($this->adapterMock); + + $this->client = new TransactionVoid($criticalLoggerMock, $this->loggerMock, $adapterFactoryMock); + } + + /** + * @return void + * + * @expectedException \Magento\Payment\Gateway\Http\ClientException + * @expectedExceptionMessage Test messages + */ + public function testPlaceRequestException() + { + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionVoid::class, + 'response' => [], + ] + ); + + $this->adapterMock->expects($this->once()) + ->method('void') + ->with($this->transactionId) + ->willThrowException(new \Exception('Test messages')); + + /** @var TransferInterface|MockObject $transferObjectMock */ + $transferObjectMock = $this->getTransferObjectMock(); + + $this->client->placeRequest($transferObjectMock); + } + + /** + * @return void + */ + public function testPlaceRequestSuccess() + { + $response = new \stdClass; + $response->success = true; + $this->adapterMock->expects($this->once()) + ->method('void') + ->with($this->transactionId) + ->willReturn($response); + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionVoid::class, + 'response' => ['success' => 1], + ] + ); + + $actualResult = $this->client->placeRequest($this->getTransferObjectMock()); + + $this->assertTrue(is_object($actualResult['object'])); + $this->assertEquals(['object' => $response], $actualResult); + } + + /** + * Creates mock object for TransferInterface. + * + * @return TransferInterface|MockObject + */ + private function getTransferObjectMock() + { + $transferObjectMock = $this->createMock(TransferInterface::class); + $transferObjectMock->expects($this->once()) + ->method('getBody') + ->willReturn($this->getTransferData()); + + return $transferObjectMock; + } + + /** + * Creates stub request data. + * + * @return array + */ + private function getTransferData() + { + return [ + 'transaction_id' => $this->transactionId, + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/AddressDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/AddressDataBuilderTest.php index 3f05aed45da60..e1bbf29c63645 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/AddressDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/AddressDataBuilderTest.php @@ -9,20 +9,21 @@ use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\AddressAdapterInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class AddressDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\AddressDataBuilder. */ class AddressDataBuilderTest extends \PHPUnit\Framework\TestCase { /** - * @var PaymentDataObjectInterface|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ private $paymentDOMock; /** - * @var OrderAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OrderAdapterInterface|MockObject */ private $orderMock; @@ -32,7 +33,7 @@ class AddressDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ private $subjectReaderMock; @@ -138,7 +139,7 @@ public function dataProviderBuild() 'city' => 'Chicago', 'region_code' => 'IL', 'country_id' => 'US', - 'post_code' => '00000' + 'post_code' => '00000', ], [ AddressDataBuilder::SHIPPING_ADDRESS => [ @@ -150,7 +151,7 @@ public function dataProviderBuild() AddressDataBuilder::LOCALITY => 'Chicago', AddressDataBuilder::REGION => 'IL', AddressDataBuilder::POSTAL_CODE => '00000', - AddressDataBuilder::COUNTRY_CODE => 'US' + AddressDataBuilder::COUNTRY_CODE => 'US', ], AddressDataBuilder::BILLING_ADDRESS => [ @@ -162,46 +163,46 @@ public function dataProviderBuild() AddressDataBuilder::LOCALITY => 'Chicago', AddressDataBuilder::REGION => 'IL', AddressDataBuilder::POSTAL_CODE => '00000', - AddressDataBuilder::COUNTRY_CODE => 'US' - ] - ] - ] + AddressDataBuilder::COUNTRY_CODE => 'US', + ], + ], + ], ]; } /** * @param array $addressData - * @return AddressAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @return AddressAdapterInterface|MockObject */ private function getAddressMock($addressData) { $addressMock = $this->createMock(AddressAdapterInterface::class); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getFirstname') ->willReturn($addressData['first_name']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getLastname') ->willReturn($addressData['last_name']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getCompany') ->willReturn($addressData['company']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getStreetLine1') ->willReturn($addressData['street_1']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getStreetLine2') ->willReturn($addressData['street_2']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getCity') ->willReturn($addressData['city']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getRegionCode') ->willReturn($addressData['region_code']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getPostcode') ->willReturn($addressData['post_code']); - $addressMock->expects(static::exactly(2)) + $addressMock->expects(self::exactly(2)) ->method('getCountryId') ->willReturn($addressData['country_id']); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php index 9799b6f18c639..84558be0dab0f 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php @@ -8,37 +8,38 @@ use Magento\Braintree\Gateway\Request\CaptureDataBuilder; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class CaptureDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\CaptureDataBuilder. */ class CaptureDataBuilderTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Braintree\Gateway\Request\CaptureDataBuilder + * @var CaptureDataBuilder */ private $builder; /** - * @var Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ - private $payment; + private $paymentMock; /** - * @var \Magento\Sales\Model\Order\Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ - private $paymentDO; + private $paymentDOMock; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ private $subjectReaderMock; protected function setUp() { - $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->getMock(); $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) @@ -57,22 +58,22 @@ public function testBuildWithException() { $amount = 10.00; $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => $amount + 'payment' => $this->paymentDOMock, + 'amount' => $amount, ]; - $this->payment->expects(static::once()) + $this->paymentMock->expects(self::once()) ->method('getCcTransId') ->willReturn(''); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(self::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); $this->builder->build($buildSubject); } @@ -87,26 +88,26 @@ public function testBuild() $expected = [ 'transaction_id' => $transactionId, - 'amount' => $amount + 'amount' => $amount, ]; $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => $amount + 'payment' => $this->paymentDOMock, + 'amount' => $amount, ]; - $this->payment->expects(static::once()) + $this->paymentMock->expects(self::once()) ->method('getCcTransId') ->willReturn($transactionId); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(self::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php index 0f25b26fd2fa3..b19715cf92010 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php @@ -9,20 +9,21 @@ use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\AddressAdapterInterface; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class CustomerDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\CustomerDataBuilder. */ class CustomerDataBuilderTest extends \PHPUnit\Framework\TestCase { /** - * @var PaymentDataObjectInterface|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ private $paymentDOMock; /** - * @var OrderAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OrderAdapterInterface|MockObject */ private $orderMock; @@ -32,7 +33,7 @@ class CustomerDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ private $subjectReaderMock; @@ -105,7 +106,7 @@ public function dataProviderBuild() 'last_name' => 'Smith', 'company' => 'Magento', 'phone' => '555-555-555', - 'email' => 'john@magento.com' + 'email' => 'john@magento.com', ], [ CustomerDataBuilder::CUSTOMER => [ @@ -114,15 +115,15 @@ public function dataProviderBuild() CustomerDataBuilder::COMPANY => 'Magento', CustomerDataBuilder::PHONE => '555-555-555', CustomerDataBuilder::EMAIL => 'john@magento.com', - ] - ] - ] + ], + ], + ], ]; } /** * @param array $billingData - * @return AddressAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @return AddressAdapterInterface|MockObject */ private function getBillingMock($billingData) { diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/DescriptorDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/DescriptorDataBuilderTest.php index 761d88b636ed7..1a87e5254bc50 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/DescriptorDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/DescriptorDataBuilderTest.php @@ -7,6 +7,9 @@ use Magento\Braintree\Gateway\Config\Config; use Magento\Braintree\Gateway\Request\DescriptorDataBuilder; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -14,10 +17,15 @@ */ class DescriptorDataBuilderTest extends \PHPUnit\Framework\TestCase { + /** + * @var SubjectReader|MockObject + */ + private $subjectReaderMock; + /** * @var Config|MockObject */ - private $config; + private $configMock; /** * @var DescriptorDataBuilder @@ -26,27 +34,41 @@ class DescriptorDataBuilderTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->config = $this->getMockBuilder(Config::class) + $this->configMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->setMethods(['getDynamicDescriptors']) ->getMock(); + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + ->disableOriginalConstructor() + ->getMock(); - $this->builder = new DescriptorDataBuilder($this->config); + $this->builder = new DescriptorDataBuilder($this->configMock, $this->subjectReaderMock); } /** - * @covers \Magento\Braintree\Gateway\Request\DescriptorDataBuilder::build * @param array $descriptors * @param array $expected * @dataProvider buildDataProvider */ public function testBuild(array $descriptors, array $expected) { - $this->config->expects(static::once()) - ->method('getDynamicDescriptors') - ->willReturn($descriptors); + $paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $buildSubject = [ + 'payment' => $paymentDOMock, + ]; + $this->subjectReaderMock->expects(self::once()) + ->method('readPayment') + ->with($buildSubject) + ->willReturn($paymentDOMock); + + $order = $this->createMock(OrderAdapterInterface::class); + $order->expects(self::once())->method('getStoreId')->willReturn('1'); + + $paymentDOMock->expects(self::once())->method('getOrder')->willReturn($order); + + $this->configMock->method('getDynamicDescriptors')->willReturn($descriptors); - $actual = $this->builder->build([]); + $actual = $this->builder->build(['payment' => $paymentDOMock]); static::assertEquals($expected, $actual); } @@ -64,42 +86,42 @@ public function buildDataProvider() 'descriptors' => [ 'name' => $name, 'phone' => $phone, - 'url' => $url + 'url' => $url, ], 'expected' => [ 'descriptor' => [ 'name' => $name, 'phone' => $phone, - 'url' => $url - ] - ] + 'url' => $url, + ], + ], ], [ 'descriptors' => [ 'name' => $name, - 'phone' => $phone + 'phone' => $phone, ], 'expected' => [ 'descriptor' => [ 'name' => $name, - 'phone' => $phone - ] - ] + 'phone' => $phone, + ], + ], ], [ 'descriptors' => [ - 'name' => $name + 'name' => $name, ], 'expected' => [ 'descriptor' => [ - 'name' => $name - ] - ] + 'name' => $name, + ], + ], ], [ 'descriptors' => [], - 'expected' => [] - ] + 'expected' => [], + ], ]; } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/KountPaymentDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/KountPaymentDataBuilderTest.php index ee0907a1ddbbb..6a4aeacba4faf 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/KountPaymentDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/KountPaymentDataBuilderTest.php @@ -5,17 +5,17 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Sales\Model\Order\Payment; use Magento\Braintree\Gateway\Config\Config; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Braintree\Gateway\Request\KountPaymentDataBuilder; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class KountPaymentDataBuilderTest - * - * @see \Magento\Braintree\Gateway\Request\KountPaymentDataBuilder + * Tests \Magento\Braintree\Gateway\Request\KountPaymentDataBuilder. */ class KountPaymentDataBuilderTest extends \PHPUnit\Framework\TestCase { @@ -27,19 +27,19 @@ class KountPaymentDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ private $configMock; /** - * @var Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ private $paymentMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ - private $paymentDO; + private $paymentDOMock; /** * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject @@ -48,7 +48,7 @@ class KountPaymentDataBuilderTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); $this->configMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); @@ -69,7 +69,7 @@ public function testBuildReadPaymentException() { $buildSubject = []; - $this->configMock->expects(static::once()) + $this->configMock->expects(self::never()) ->method('hasFraudProtection') ->willReturn(true); @@ -84,31 +84,34 @@ public function testBuildReadPaymentException() public function testBuild() { $additionalData = [ - DataAssignObserver::DEVICE_DATA => self::DEVICE_DATA + DataAssignObserver::DEVICE_DATA => self::DEVICE_DATA, ]; $expectedResult = [ KountPaymentDataBuilder::DEVICE_DATA => self::DEVICE_DATA, ]; - $buildSubject = ['payment' => $this->paymentDO]; + $order = $this->createMock(OrderAdapterInterface::class); + $this->paymentDOMock->expects(self::once())->method('getOrder')->willReturn($order); - $this->paymentMock->expects(static::exactly(count($additionalData))) + $buildSubject = ['payment' => $this->paymentDOMock]; + + $this->paymentMock->expects(self::exactly(count($additionalData))) ->method('getAdditionalInformation') ->willReturn($additionalData); - $this->configMock->expects(static::once()) + $this->configMock->expects(self::once()) ->method('hasFraudProtection') ->willReturn(true); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(self::once()) ->method('getPayment') ->willReturn($this->paymentMock); $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); static::assertEquals( $expectedResult, diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/DeviceDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/DeviceDataBuilderTest.php index fba65354d6095..c618ab66b95bc 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/DeviceDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/DeviceDataBuilderTest.php @@ -5,31 +5,31 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request\PayPal; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\PayPal\DeviceDataBuilder; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Model\InfoInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class DeviceDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\PayPal\DeviceDataBuilder. */ class DeviceDataBuilderTest extends \PHPUnit\Framework\TestCase { /** * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; /** * @var PaymentDataObjectInterface|MockObject */ - private $paymentDataObject; + private $paymentDataObjectMock; /** * @var InfoInterface|MockObject */ - private $paymentInfo; + private $paymentInfoMock; /** * @var DeviceDataBuilder @@ -38,16 +38,16 @@ class DeviceDataBuilderTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->setMethods(['readPayment']) ->getMock(); - $this->paymentDataObject = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentDataObjectMock = $this->createMock(PaymentDataObjectInterface::class); - $this->paymentInfo = $this->createMock(InfoInterface::class); + $this->paymentInfoMock = $this->createMock(InfoInterface::class); - $this->builder = new DeviceDataBuilder($this->subjectReader); + $this->builder = new DeviceDataBuilder($this->subjectReaderMock); } /** @@ -59,19 +59,19 @@ protected function setUp() public function testBuild(array $paymentData, array $expected) { $subject = [ - 'payment' => $this->paymentDataObject + 'payment' => $this->paymentDataObjectMock, ]; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPayment') ->with($subject) - ->willReturn($this->paymentDataObject); + ->willReturn($this->paymentDataObjectMock); - $this->paymentDataObject->expects(static::once()) + $this->paymentDataObjectMock->expects(static::once()) ->method('getPayment') - ->willReturn($this->paymentInfo); + ->willReturn($this->paymentInfoMock); - $this->paymentInfo->expects(static::once()) + $this->paymentInfoMock->expects(static::once()) ->method('getAdditionalInformation') ->willReturn($paymentData); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/VaultDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/VaultDataBuilderTest.php index 8e83254727bf7..5595d5172b194 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/VaultDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PayPal/VaultDataBuilderTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request\PayPal; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\PayPal\VaultDataBuilder; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Model\InfoInterface; @@ -13,24 +13,24 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class VaultDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\PayPal\VaultDataBuilder. */ class VaultDataBuilderTest extends \PHPUnit\Framework\TestCase { /** * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; /** * @var PaymentDataObjectInterface|MockObject */ - private $paymentDataObject; + private $paymentDataObjectMock; /** * @var InfoInterface|MockObject */ - private $paymentInfo; + private $paymentInfoMock; /** * @var VaultDataBuilder @@ -39,16 +39,16 @@ class VaultDataBuilderTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->paymentDataObject = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentDataObjectMock = $this->createMock(PaymentDataObjectInterface::class); - $this->paymentInfo = $this->createMock(InfoInterface::class); + $this->paymentInfoMock = $this->createMock(InfoInterface::class); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->setMethods(['readPayment']) ->getMock(); - $this->builder = new VaultDataBuilder($this->subjectReader); + $this->builder = new VaultDataBuilder($this->subjectReaderMock); } /** @@ -60,19 +60,19 @@ protected function setUp() public function testBuild(array $additionalInfo, array $expected) { $subject = [ - 'payment' => $this->paymentDataObject + 'payment' => $this->paymentDataObjectMock, ]; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPayment') ->with($subject) - ->willReturn($this->paymentDataObject); + ->willReturn($this->paymentDataObjectMock); - $this->paymentDataObject->expects(static::once()) + $this->paymentDataObjectMock->expects(static::once()) ->method('getPayment') - ->willReturn($this->paymentInfo); + ->willReturn($this->paymentInfoMock); - $this->paymentInfo->expects(static::once()) + $this->paymentInfoMock->expects(static::once()) ->method('getAdditionalInformation') ->willReturn($additionalInfo); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php index 12c613b8f216b..5620e8ffa92b8 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php @@ -5,23 +5,21 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request; -use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; use Magento\Braintree\Gateway\Request\PaymentDataBuilder; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Braintree\Gateway\Config\Config; /** - * Class PaymentDataBuilderTest - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * Tests \Magento\Braintree\Gateway\Request\PaymentDataBuilder. */ class PaymentDataBuilderTest extends \PHPUnit\Framework\TestCase { const PAYMENT_METHOD_NONCE = 'nonce'; - const MERCHANT_ACCOUNT_ID = '245345'; /** * @var PaymentDataBuilder @@ -29,36 +27,31 @@ class PaymentDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject - */ - private $configMock; - - /** - * @var Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ private $paymentMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ - private $paymentDO; + private $paymentDOMock; /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject */ private $subjectReaderMock; /** - * @var OrderAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OrderAdapterInterface|MockObject */ private $orderMock; + /** + * @inheritdoc + */ protected function setUp() { - $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $this->configMock = $this->getMockBuilder(Config::class) - ->disableOriginalConstructor() - ->getMock(); + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->getMock(); @@ -67,13 +60,19 @@ protected function setUp() ->getMock(); $this->orderMock = $this->createMock(OrderAdapterInterface::class); - $this->builder = new PaymentDataBuilder($this->configMock, $this->subjectReaderMock); + /** @var Config $config */ + $config = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->builder = new PaymentDataBuilder($config, $this->subjectReaderMock); } /** + * @return void * @expectedException \InvalidArgumentException */ - public function testBuildReadPaymentException() + public function testBuildReadPaymentException(): void { $buildSubject = []; @@ -86,19 +85,20 @@ public function testBuildReadPaymentException() } /** + * @return void * @expectedException \InvalidArgumentException */ - public function testBuildReadAmountException() + public function testBuildReadAmountException(): void { $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => null + 'payment' => $this->paymentDOMock, + 'amount' => null, ]; $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) @@ -107,57 +107,55 @@ public function testBuildReadAmountException() $this->builder->build($buildSubject); } - public function testBuild() + /** + * @return void + */ + public function testBuild(): void { $additionalData = [ [ DataAssignObserver::PAYMENT_METHOD_NONCE, - self::PAYMENT_METHOD_NONCE - ] + self::PAYMENT_METHOD_NONCE, + ], ]; $expectedResult = [ PaymentDataBuilder::AMOUNT => 10.00, PaymentDataBuilder::PAYMENT_METHOD_NONCE => self::PAYMENT_METHOD_NONCE, - PaymentDataBuilder::ORDER_ID => '000000101', - PaymentDataBuilder::MERCHANT_ACCOUNT_ID => self::MERCHANT_ACCOUNT_ID, + PaymentDataBuilder::ORDER_ID => '000000101' ]; $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => 10.00 + 'payment' => $this->paymentDOMock, + 'amount' => 10.00, ]; - $this->paymentMock->expects(static::exactly(count($additionalData))) + $this->paymentMock->expects(self::exactly(count($additionalData))) ->method('getAdditionalInformation') ->willReturnMap($additionalData); - $this->configMock->expects(static::once()) - ->method('getMerchantAccountId') - ->willReturn(self::MERCHANT_ACCOUNT_ID); - - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(self::once()) ->method('getPayment') ->willReturn($this->paymentMock); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(self::once()) ->method('getOrder') ->willReturn($this->orderMock); $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willReturn(10.00); - $this->orderMock->expects(static::once()) + $this->orderMock->expects(self::once()) ->method('getOrderIncrementId') ->willReturn('000000101'); - static::assertEquals( + self::assertEquals( $expectedResult, $this->builder->build($buildSubject) ); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/RefundDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/RefundDataBuilderTest.php index 5aa383d095a1e..dffe293c5a32f 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/RefundDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/RefundDataBuilderTest.php @@ -5,65 +5,78 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request; -use Magento\Braintree\Gateway\Helper\SubjectReader; use Magento\Braintree\Gateway\Request\PaymentDataBuilder; use Magento\Braintree\Gateway\Request\RefundDataBuilder; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Model\Order\Payment; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +/** + * Tests \Magento\Braintree\Gateway\Request\RefundDataBuilder. + */ class RefundDataBuilderTest extends \PHPUnit\Framework\TestCase { /** - * @var SubjectReader | \PHPUnit_Framework_MockObject_MockObject + * @var SubjectReader|MockObject + */ + private $subjectReaderMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var Payment|MockObject */ - private $subjectReader; + private $paymentModelMock; /** * @var RefundDataBuilder */ private $dataBuilder; + /** + * @var string + */ + private $transactionId = 'xsd7n'; + public function setUp() { - $this->subjectReader = $this->getMockBuilder( - SubjectReader::class - )->disableOriginalConstructor() + $this->paymentModelMock = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() ->getMock(); - $this->dataBuilder = new RefundDataBuilder($this->subjectReader); + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->dataBuilder = new RefundDataBuilder($this->subjectReaderMock); } public function testBuild() { - $paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $paymentModel = $this->getMockBuilder( - Payment::class - )->disableOriginalConstructor() - ->getMock(); + $this->initPaymentDOMock(); + $buildSubject = ['payment' => $this->paymentDOMock, 'amount' => 12.358]; - $buildSubject = ['payment' => $paymentDO, 'amount' => 12.358]; - $transactionId = 'xsd7n'; - - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($paymentDO); - $paymentDO->expects(static::once()) - ->method('getPayment') - ->willReturn($paymentModel); - $paymentModel->expects(static::once()) + ->willReturn($this->paymentDOMock); + $this->paymentModelMock->expects(self::once()) ->method('getParentTransactionId') - ->willReturn($transactionId); - $this->subjectReader->expects(static::once()) + ->willReturn($this->transactionId); + $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willReturn($buildSubject['amount']); static::assertEquals( [ - 'transaction_id' => $transactionId, - PaymentDataBuilder::AMOUNT => '12.36' + 'transaction_id' => $this->transactionId, + PaymentDataBuilder::AMOUNT => '12.36', ], $this->dataBuilder->build($buildSubject) ); @@ -71,34 +84,25 @@ public function testBuild() public function testBuildNullAmount() { - $paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $paymentModel = $this->getMockBuilder( - Payment::class - )->disableOriginalConstructor() - ->getMock(); - - $buildSubject = ['payment' => $paymentDO]; - $transactionId = 'xsd7n'; + $this->initPaymentDOMock(); + $buildSubject = ['payment' => $this->paymentDOMock]; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($paymentDO); - $paymentDO->expects(static::once()) - ->method('getPayment') - ->willReturn($paymentModel); - $paymentModel->expects(static::once()) + ->willReturn($this->paymentDOMock); + $this->paymentModelMock->expects(self::once()) ->method('getParentTransactionId') - ->willReturn($transactionId); - $this->subjectReader->expects(static::once()) + ->willReturn($this->transactionId); + $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willThrowException(new \InvalidArgumentException()); static::assertEquals( [ - 'transaction_id' => $transactionId, - PaymentDataBuilder::AMOUNT => null + 'transaction_id' => $this->transactionId, + PaymentDataBuilder::AMOUNT => null, ], $this->dataBuilder->build($buildSubject) ); @@ -106,37 +110,41 @@ public function testBuildNullAmount() public function testBuildCutOffLegacyTransactionIdPostfix() { - $paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $paymentModel = $this->getMockBuilder( - Payment::class - )->disableOriginalConstructor() - ->getMock(); - - $buildSubject = ['payment' => $paymentDO]; + $this->initPaymentDOMock(); + $buildSubject = ['payment' => $this->paymentDOMock]; $legacyTxnId = 'xsd7n-' . TransactionInterface::TYPE_CAPTURE; - $transactionId = 'xsd7n'; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($paymentDO); - $paymentDO->expects(static::once()) - ->method('getPayment') - ->willReturn($paymentModel); - $paymentModel->expects(static::once()) + ->willReturn($this->paymentDOMock); + $this->paymentModelMock->expects(self::once()) ->method('getParentTransactionId') ->willReturn($legacyTxnId); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willThrowException(new \InvalidArgumentException()); static::assertEquals( [ - 'transaction_id' => $transactionId, - PaymentDataBuilder::AMOUNT => null + 'transaction_id' => $this->transactionId, + PaymentDataBuilder::AMOUNT => null, ], $this->dataBuilder->build($buildSubject) ); } + + /** + * Creates mock object for PaymentDataObjectInterface + * + * @return PaymentDataObjectInterface|MockObject + */ + private function initPaymentDOMock() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentDOMock->expects(self::once()) + ->method('getPayment') + ->willReturn($this->paymentModelMock); + } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/ThreeDSecureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/ThreeDSecureDataBuilderTest.php index c28ac0c3ac372..f12d1365d0b34 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/ThreeDSecureDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/ThreeDSecureDataBuilderTest.php @@ -6,14 +6,15 @@ namespace Magento\Braintree\Test\Unit\Gateway\Request; use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\ThreeDSecureDataBuilder; -use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; -use Magento\Payment\Gateway\Data\Order\OrderAdapter; use Magento\Payment\Gateway\Data\Order\AddressAdapter; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Data\Order\OrderAdapter; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ThreeDSecureDataBuilderTest + * Tests \Magento\Braintree\Gateway\Request\ThreeDSecureDataBuilder. */ class ThreeDSecureDataBuilderTest extends \PHPUnit\Framework\TestCase { @@ -23,41 +24,49 @@ class ThreeDSecureDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ private $configMock; /** - * @var PaymentDataObjectInterface|\PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ - private $paymentDO; + private $paymentDOMock; /** - * @var OrderAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var OrderAdapter|MockObject */ - private $order; + private $orderMock; /** - * @var \Magento\Payment\Gateway\Data\Order\AddressAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var AddressAdapter|MockObject */ - private $billingAddress; + private $billingAddressMock; /** * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject */ private $subjectReaderMock; + /** + * @var int + */ + private $storeId = 1; + + /** + * @inheritdoc + */ protected function setUp() { $this->initOrderMock(); - $this->paymentDO = $this->getMockBuilder(PaymentDataObjectInterface::class) + $this->paymentDOMock = $this->getMockBuilder(PaymentDataObjectInterface::class) ->disableOriginalConstructor() ->setMethods(['getOrder', 'getPayment']) ->getMock(); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(static::once()) ->method('getOrder') - ->willReturn($this->order); + ->willReturn($this->orderMock); $this->configMock = $this->getMockBuilder(Config::class) ->setMethods(['isVerify3DSecure', 'getThresholdAmount', 'get3DSecureSpecificCountries']) @@ -82,41 +91,45 @@ protected function setUp() public function testBuild($verify, $thresholdAmount, $countryId, array $countries, array $expected) { $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => 25 + 'payment' => $this->paymentDOMock, + 'amount' => 25, ]; $this->configMock->expects(static::once()) ->method('isVerify3DSecure') + ->with(self::equalTo($this->storeId)) ->willReturn($verify); $this->configMock->expects(static::any()) ->method('getThresholdAmount') + ->with(self::equalTo($this->storeId)) ->willReturn($thresholdAmount); $this->configMock->expects(static::any()) ->method('get3DSecureSpecificCountries') + ->with(self::equalTo($this->storeId)) ->willReturn($countries); - $this->billingAddress->expects(static::any()) + $this->billingAddressMock->expects(static::any()) ->method('getCountryId') ->willReturn($countryId); $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); + ->willReturn($this->paymentDOMock); $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willReturn(25); $result = $this->builder->build($buildSubject); - static::assertEquals($expected, $result); + self::assertEquals($expected, $result); } /** - * Get list of variations for build test + * Gets list of variations to build request data. + * * @return array */ public function buildDataProvider() @@ -144,22 +157,26 @@ public function buildDataProvider() } /** - * Create mock object for order adapter + * Creates mock object for order adapter. + * + * @return void */ private function initOrderMock() { - $this->billingAddress = $this->getMockBuilder(AddressAdapter::class) + $this->billingAddressMock = $this->getMockBuilder(AddressAdapter::class) ->disableOriginalConstructor() ->setMethods(['getCountryId']) ->getMock(); - $this->order = $this->getMockBuilder(OrderAdapter::class) + $this->orderMock = $this->getMockBuilder(OrderAdapter::class) ->disableOriginalConstructor() - ->setMethods(['getBillingAddress']) + ->setMethods(['getBillingAddress', 'getStoreId']) ->getMock(); - $this->order->expects(static::any()) + $this->orderMock->expects(static::any()) ->method('getBillingAddress') - ->willReturn($this->billingAddress); + ->willReturn($this->billingAddressMock); + $this->orderMock->method('getStoreId') + ->willReturn($this->storeId); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php index df11938ddba70..25ccd8b32d10e 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php @@ -5,13 +5,17 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Api\Data\OrderPaymentExtension; use Magento\Sales\Model\Order\Payment; use Magento\Vault\Model\PaymentToken; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +/** + * Tests \Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder. + */ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase { /** @@ -20,35 +24,38 @@ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase private $builder; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var PaymentDataObjectInterface|MockObject */ - private $paymentDO; + private $paymentDOMock; /** - * @var Payment|\PHPUnit_Framework_MockObject_MockObject + * @var Payment|MockObject */ - private $payment; + private $paymentMock; /** * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject */ - private $subjectReader; + private $subjectReaderMock; - public function setUp() + /** + * @inheritdoc + */ + protected function setUp() { - $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class); - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->getMock(); - $this->paymentDO->expects(static::once()) + $this->paymentDOMock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->getMock(); - $this->builder = new VaultCaptureDataBuilder($this->subjectReader); + $this->builder = new VaultCaptureDataBuilder($this->subjectReaderMock); } /** @@ -59,45 +66,45 @@ public function testBuild() $amount = 30.00; $token = '5tfm4c'; $buildSubject = [ - 'payment' => $this->paymentDO, - 'amount' => $amount + 'payment' => $this->paymentDOMock, + 'amount' => $amount, ]; $expected = [ 'amount' => $amount, - 'paymentMethodToken' => $token + 'paymentMethodToken' => $token, ]; - $this->subjectReader->expects(self::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($buildSubject) - ->willReturn($this->paymentDO); - $this->subjectReader->expects(self::once()) + ->willReturn($this->paymentDOMock); + $this->subjectReaderMock->expects(self::once()) ->method('readAmount') ->with($buildSubject) ->willReturn($amount); - $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class) + $paymentExtensionMock = $this->getMockBuilder(OrderPaymentExtension::class) ->setMethods(['getVaultPaymentToken']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $paymentToken = $this->getMockBuilder(PaymentToken::class) + $paymentTokenMock = $this->getMockBuilder(PaymentToken::class) ->disableOriginalConstructor() ->getMock(); - $paymentExtension->expects(static::once()) + $paymentExtensionMock->expects(static::once()) ->method('getVaultPaymentToken') - ->willReturn($paymentToken); - $this->payment->expects(static::once()) + ->willReturn($paymentTokenMock); + $this->paymentMock->expects(static::once()) ->method('getExtensionAttributes') - ->willReturn($paymentExtension); + ->willReturn($paymentExtensionMock); - $paymentToken->expects(static::once()) + $paymentTokenMock->expects(static::once()) ->method('getGatewayToken') ->willReturn($token); $result = $this->builder->build($buildSubject); - static::assertEquals($expected, $result); + self::assertEquals($expected, $result); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VoidDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VoidDataBuilderTest.php new file mode 100644 index 0000000000000..88713885b5c7d --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VoidDataBuilderTest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Gateway\Request; + +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Request\VoidDataBuilder; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests \Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder. + */ +class VoidDataBuilderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var VoidDataBuilder + */ + private $builder; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + */ + private $subjectReaderMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->getMock(); + $this->paymentDOMock->expects(static::once()) + ->method('getPayment') + ->willReturn($this->paymentMock); + + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->builder = new VoidDataBuilder($this->subjectReaderMock); + } + + /** + * @param string|null $parentTransactionId + * @param string $callLastTransId + * @param string $lastTransId + * @param string $expected + * @return void + * @dataProvider buildDataProvider + */ + public function testBuild($parentTransactionId, $callLastTransId, $lastTransId, $expected) + { + $amount = 30.00; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'amount' => $amount, + ]; + + $this->subjectReaderMock->expects(self::once()) + ->method('readPayment') + ->with($buildSubject) + ->willReturn($this->paymentDOMock); + + $this->paymentMock->expects(self::once()) + ->method('getParentTransactionId') + ->willReturn($parentTransactionId); + $this->paymentMock->expects(self::$callLastTransId()) + ->method('getLastTransId') + ->willReturn($lastTransId); + + $result = $this->builder->build($buildSubject); + self::assertEquals( + ['transaction_id' => $expected], + $result + ); + } + + /** + * @return array + */ + public function buildDataProvider() + { + return [ + [ + 'parentTransactionId' => 'b3b99d', + 'callLastTransId' => 'never', + 'lastTransId' => 'd45d22', + 'expected' => 'b3b99d', + ], + [ + 'parentTransactionId' => null, + 'callLastTransId' => 'once', + 'expected' => 'd45d22', + 'lastTransId' => 'd45d22', + ], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CancelDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CancelDetailsHandlerTest.php new file mode 100644 index 0000000000000..2fa3d2ea65836 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CancelDetailsHandlerTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Gateway\Response; + +use Magento\Braintree\Gateway\Response\CancelDetailsHandler; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests \Magento\Braintree\Gateway\Response\CancelDetailsHandler. + */ +class CancelDetailsHandlerTest extends TestCase +{ + /** + * @var CancelDetailsHandler + */ + private $handler; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->handler = new CancelDetailsHandler(new SubjectReader()); + } + + /** + * Checks a case when cancel handler closes the current and parent transactions. + * + * @return void + */ + public function testHandle(): void + { + /** @var OrderAdapterInterface|MockObject $order */ + $order = $this->getMockForAbstractClass(OrderAdapterInterface::class); + /** @var Payment|MockObject $payment */ + $payment = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->setMethods(['setOrder']) + ->getMock(); + + $paymentDO = new PaymentDataObject($order, $payment); + $response = [ + 'payment' => $paymentDO, + ]; + + $this->handler->handle($response, []); + + self::assertTrue($payment->getIsTransactionClosed(), 'The current transaction should be closed.'); + self::assertTrue($payment->getShouldCloseParentTransaction(), 'The parent transaction should be closed.'); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php index 87e8e4e413c1b..a70993e14e50c 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php @@ -5,16 +5,15 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Response; -use Braintree\Result\Successful; use Braintree\Transaction; use Magento\Braintree\Gateway\Response\CardDetailsHandler; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Sales\Model\Order\Payment; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; /** - * Class CardDetailsHandlerTest + * Tests \Magento\Braintree\Gateway\Response\CardDetailsHandler. */ class CardDetailsHandlerTest extends \PHPUnit\Framework\TestCase { @@ -26,12 +25,12 @@ class CardDetailsHandlerTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Sales\Model\Order\Payment|\PHPUnit_Framework_MockObject_MockObject */ - private $payment; + private $paymentMock; /** * @var \Magento\Braintree\Gateway\Config\Config|\PHPUnit_Framework_MockObject_MockObject */ - private $config; + private $configMock; /** * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject @@ -45,7 +44,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->cardHandler = new CardDetailsHandler($this->config, $this->subjectReaderMock); + $this->cardHandler = new CardDetailsHandler($this->configMock, $this->subjectReaderMock); } /** @@ -53,30 +52,30 @@ protected function setUp() */ public function testHandle() { - $paymentData = $this->getPaymentDataObjectMock(); + $paymentDataMock = $this->getPaymentDataObjectMock(); $transaction = $this->getBraintreeTransaction(); - $subject = ['payment' => $paymentData]; + $subject = ['payment' => $paymentDataMock]; $response = ['object' => $transaction]; $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($subject) - ->willReturn($paymentData); + ->willReturn($paymentDataMock); $this->subjectReaderMock->expects(self::once()) ->method('readTransaction') ->with($response) ->willReturn($transaction); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setCcLast4'); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setCcExpMonth'); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setCcExpYear'); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setCcType'); - $this->payment->expects(static::exactly(2)) + $this->paymentMock->expects(static::exactly(2)) ->method('setAdditionalInformation'); $this->cardHandler->handle($subject, $response); @@ -87,12 +86,12 @@ public function testHandle() */ private function initConfigMock() { - $this->config = $this->getMockBuilder(Config::class) + $this->configMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->setMethods(['getCctypesMapper']) ->getMock(); - $this->config->expects(static::once()) + $this->configMock->expects(static::once()) ->method('getCctypesMapper') ->willReturn([ 'american-express' => 'AE', @@ -110,7 +109,7 @@ private function initConfigMock() */ private function getPaymentDataObjectMock() { - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->setMethods([ 'setCcLast4', @@ -128,7 +127,7 @@ private function getPaymentDataObjectMock() $mock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); return $mock; } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPal/VaultDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPal/VaultDetailsHandlerTest.php index fdf3dc941bd77..b3a7f8b9ee76a 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPal/VaultDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPal/VaultDetailsHandlerTest.php @@ -5,9 +5,10 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Response\PayPal; +use Braintree\Result\Successful; use Braintree\Transaction; use Braintree\Transaction\PayPalDetails; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Response\PayPal\VaultDetailsHandler; use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -15,54 +16,54 @@ use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; use Magento\Sales\Model\Order\Payment; +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; -use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; -use Magento\Vault\Model\AccountPaymentTokenFactory; use Magento\Vault\Model\PaymentToken; +use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class VaultDetailsHandlerTest + * Tests \Magento\Braintree\Gateway\Response\PayPal\VaultDetailsHandler. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class VaultDetailsHandlerTest extends \PHPUnit\Framework\TestCase +class VaultDetailsHandlerTest extends TestCase { private static $transactionId = '1n2suy'; - /** - * @var SubjectReader|MockObject - */ - private $subjectReader; + private static $token = 'rc39al'; + + private static $payerEmail = 'john.doe@example.com'; /** * @var PaymentDataObjectInterface|MockObject */ - private $paymentDataObject; + private $paymentDataObjectMock; /** * @var Payment|MockObject */ - private $paymentInfo; + private $paymentInfoMock; /** - * @var AccountPaymentTokenFactory|MockObject + * @var PaymentTokenFactoryInterface|MockObject */ - private $paymentTokenFactory; + private $paymentTokenFactoryMock; /** * @var PaymentTokenInterface|MockObject */ - protected $paymentToken; + protected $paymentTokenMock; /** * @var OrderPaymentExtension|MockObject */ - private $paymentExtension; + private $paymentExtensionMock; /** * @var OrderPaymentExtensionInterfaceFactory|MockObject */ - private $paymentExtensionFactory; + private $paymentExtensionFactoryMock; /** * @var VaultDetailsHandler @@ -72,7 +73,7 @@ class VaultDetailsHandlerTest extends \PHPUnit\Framework\TestCase /** * @var DateTimeFactory|MockObject */ - private $dateTimeFactory; + private $dateTimeFactoryMock; /** * @var array @@ -83,146 +84,119 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->paymentDataObject = $this->getMockForAbstractClass(PaymentDataObjectInterface::class); + $this->paymentDataObjectMock = $this->getMockForAbstractClass(PaymentDataObjectInterface::class); - $this->paymentInfo = $this->getMockBuilder(Payment::class) + $this->paymentInfoMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() - ->setMethods(['__wakeup']) + ->setMethods(['__wakeup', 'getExtensionAttributes']) ->getMock(); - $this->paymentToken = $objectManager->getObject(PaymentToken::class); + $this->paymentTokenMock = $objectManager->getObject(PaymentToken::class); - $this->paymentTokenFactory = $this->getMockBuilder(AccountPaymentTokenFactory::class) + $this->paymentTokenFactoryMock = $this->getMockBuilder(PaymentTokenFactoryInterface::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->paymentExtension = $this->getMockBuilder(OrderPaymentExtensionInterface::class) + $this->paymentExtensionMock = $this->getMockBuilder(OrderPaymentExtensionInterface::class) ->setMethods(['setVaultPaymentToken', 'getVaultPaymentToken']) ->disableOriginalConstructor() ->getMock(); - $this->paymentExtensionFactory = $this->getMockBuilder(OrderPaymentExtensionInterfaceFactory::class) + $this->paymentExtensionFactoryMock = $this->getMockBuilder(OrderPaymentExtensionInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); + $this->paymentInfoMock->expects(self::any()) + ->method('getExtensionAttributes') + ->willReturn($this->paymentExtensionMock); + $this->subject = [ - 'payment' => $this->paymentDataObject, + 'payment' => $this->paymentDataObjectMock, ]; - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->setMethods(['readPayment', 'readTransaction']) - ->getMock(); - $this->subjectReader->expects(static::once()) - ->method('readPayment') - ->with($this->subject) - ->willReturn($this->paymentDataObject); - $this->dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class) + $this->dateTimeFactoryMock = $this->getMockBuilder(DateTimeFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $this->handler = new VaultDetailsHandler( - $this->paymentTokenFactory, - $this->paymentExtensionFactory, - $this->subjectReader, - $this->dateTimeFactory + $this->paymentTokenFactoryMock, + $this->paymentExtensionFactoryMock, + new SubjectReader(), + $this->dateTimeFactoryMock ); } - /** - * @covers \Magento\Braintree\Gateway\Response\PayPal\VaultDetailsHandler::handle - */ public function testHandle() { - /** @var Transaction $transaction */ $transaction = $this->getTransaction(); $response = [ 'object' => $transaction ]; - $this->paymentExtension->expects(static::once()) - ->method('setVaultPaymentToken') - ->with($this->paymentToken); - $this->paymentExtension->expects(static::once()) - ->method('getVaultPaymentToken') - ->willReturn($this->paymentToken); - - $this->subjectReader->expects(static::once()) - ->method('readTransaction') - ->with($response) - ->willReturn($transaction); + $this->paymentExtensionMock->method('setVaultPaymentToken') + ->with($this->paymentTokenMock); + $this->paymentExtensionMock->method('getVaultPaymentToken') + ->willReturn($this->paymentTokenMock); - $this->paymentDataObject->expects(static::once()) - ->method('getPayment') - ->willReturn($this->paymentInfo); + $this->paymentDataObjectMock->method('getPayment') + ->willReturn($this->paymentInfoMock); - $this->paymentTokenFactory->expects(static::once()) - ->method('create') - ->willReturn($this->paymentToken); + $this->paymentTokenFactoryMock->method('create') + ->with(PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT) + ->willReturn($this->paymentTokenMock); - $this->paymentExtensionFactory->expects(static::once()) - ->method('create') - ->willReturn($this->paymentExtension); + $this->paymentExtensionFactoryMock->method('create') + ->willReturn($this->paymentExtensionMock); $dateTime = new \DateTime('2016-07-05 00:00:00', new \DateTimeZone('UTC')); $expirationDate = '2017-07-05 00:00:00'; - $this->dateTimeFactory->expects(static::once()) - ->method('create') + $this->dateTimeFactoryMock->method('create') ->willReturn($dateTime); $this->handler->handle($this->subject, $response); - $extensionAttributes = $this->paymentInfo->getExtensionAttributes(); - /** @var PaymentTokenInterface $paymentToken */ + $extensionAttributes = $this->paymentInfoMock->getExtensionAttributes(); $paymentToken = $extensionAttributes->getVaultPaymentToken(); - static::assertNotNull($paymentToken); + self::assertNotNull($paymentToken); $tokenDetails = json_decode($paymentToken->getTokenDetails(), true); - static::assertSame($this->paymentToken, $paymentToken); - static::assertEquals($transaction->paypalDetails->token, $paymentToken->getGatewayToken()); - static::assertEquals($transaction->paypalDetails->payerEmail, $tokenDetails['payerEmail']); - static::assertEquals($expirationDate, $paymentToken->getExpiresAt()); + self::assertSame($this->paymentTokenMock, $paymentToken); + self::assertEquals(self::$token, $paymentToken->getGatewayToken()); + self::assertEquals(self::$payerEmail, $tokenDetails['payerEmail']); + self::assertEquals($expirationDate, $paymentToken->getExpiresAt()); } - /** - * @covers \Magento\Braintree\Gateway\Response\PayPal\VaultDetailsHandler::handle - */ public function testHandleWithoutToken() { $transaction = $this->getTransaction(); - $transaction->paypalDetails->token = null; + $transaction->transaction->paypalDetails->token = null; $response = [ 'object' => $transaction ]; - $this->subjectReader->expects(static::once()) - ->method('readTransaction') - ->with($response) - ->willReturn($transaction); - - $this->paymentDataObject->expects(static::once()) - ->method('getPayment') - ->willReturn($this->paymentInfo); + $this->paymentDataObjectMock->method('getPayment') + ->willReturn($this->paymentInfoMock); - $this->paymentTokenFactory->expects(static::never()) + $this->paymentTokenFactoryMock->expects(self::never()) ->method('create'); - $this->dateTimeFactory->expects(static::never()) + $this->dateTimeFactoryMock->expects(self::never()) ->method('create'); $this->handler->handle($this->subject, $response); - static::assertNull($this->paymentInfo->getExtensionAttributes()); + self::assertNotNull($this->paymentInfoMock->getExtensionAttributes()); } /** - * Create Braintree transaction - * @return Transaction + * Creates Braintree transaction. + * + * @return Successful */ - private function getTransaction() + private function getTransaction(): Successful { $attributes = [ 'id' => self::$transactionId, @@ -230,19 +204,21 @@ private function getTransaction() ]; $transaction = Transaction::factory($attributes); + $result = new Successful(['transaction' => $transaction]); - return $transaction; + return $result; } /** - * Get PayPal transaction details + * Gets PayPal transaction details. + * * @return PayPalDetails */ - private function getPayPalDetails() + private function getPayPalDetails(): PayPalDetails { $attributes = [ - 'token' => 'rc39al', - 'payerEmail' => 'john.doe@example.com' + 'token' => self::$token, + 'payerEmail' => self::$payerEmail ]; $details = new PayPalDetails($attributes); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPalDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPalDetailsHandlerTest.php index f1420ee895e5b..1b2c8c6bb4ad1 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPalDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PayPalDetailsHandlerTest.php @@ -8,9 +8,8 @@ use Braintree\Transaction; use Magento\Braintree\Gateway\Response\PayPalDetailsHandler; use Magento\Payment\Gateway\Data\PaymentDataObject; -use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -26,26 +25,26 @@ class PayPalDetailsHandlerTest extends \PHPUnit\Framework\TestCase /** * @var Payment|MockObject */ - private $payment; + private $paymentMock; /** * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; protected function setUp() { - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->setMethods([ 'setAdditionalInformation', ]) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->getMock(); - $this->payPalHandler = new PayPalDetailsHandler($this->subjectReader); + $this->payPalHandler = new PayPalDetailsHandler($this->subjectReaderMock); } /** @@ -53,26 +52,26 @@ protected function setUp() */ public function testHandle() { - $paymentData = $this->getPaymentDataObjectMock(); + $paymentDataMock = $this->getPaymentDataObjectMock(); $transaction = $this->getBraintreeTransaction(); - $subject = ['payment' => $paymentData]; + $subject = ['payment' => $paymentDataMock]; $response = ['object' => $transaction]; - $this->subjectReader->expects(self::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($subject) - ->willReturn($paymentData); - $this->subjectReader->expects(self::once()) + ->willReturn($paymentDataMock); + $this->subjectReaderMock->expects(self::once()) ->method('readTransaction') ->with($response) ->willReturn($transaction); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPayPal') ->with($transaction) ->willReturn($transaction->paypal); - $this->payment->expects(static::exactly(2)) + $this->paymentMock->expects(static::exactly(2)) ->method('setAdditionalInformation'); $this->payPalHandler->handle($subject, $response); @@ -91,7 +90,7 @@ private function getPaymentDataObjectMock() $mock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); return $mock; } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php index d90caa84b447b..69beab38f001b 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php @@ -8,13 +8,12 @@ use Braintree\Transaction; use Magento\Braintree\Gateway\Response\PaymentDetailsHandler; use Magento\Payment\Gateway\Data\PaymentDataObject; -use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class PaymentDetailsHandlerTest + * Tests \\Magento\Braintree\Gateway\Response\PaymentDetailsHandler. */ class PaymentDetailsHandlerTest extends \PHPUnit\Framework\TestCase { @@ -28,35 +27,35 @@ class PaymentDetailsHandlerTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Sales\Model\Order\Payment|MockObject */ - private $payment; + private $paymentMock; /** * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; protected function setUp() { - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->setMethods([ 'setCcTransId', 'setLastTransId', - 'setAdditionalInformation' + 'setAdditionalInformation', ]) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->getMock(); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setCcTransId'); - $this->payment->expects(static::once()) + $this->paymentMock->expects(static::once()) ->method('setLastTransId'); - $this->payment->expects(static::any()) + $this->paymentMock->expects(static::any()) ->method('setAdditionalInformation'); - $this->paymentHandler = new PaymentDetailsHandler($this->subjectReader); + $this->paymentHandler = new PaymentDetailsHandler($this->subjectReaderMock); } /** @@ -64,17 +63,17 @@ protected function setUp() */ public function testHandle() { - $paymentData = $this->getPaymentDataObjectMock(); + $paymentDataMock = $this->getPaymentDataObjectMock(); $transaction = $this->getBraintreeTransaction(); - $subject = ['payment' => $paymentData]; + $subject = ['payment' => $paymentDataMock]; $response = ['object' => $transaction]; - $this->subjectReader->expects(self::once()) + $this->subjectReaderMock->expects(self::once()) ->method('readPayment') ->with($subject) - ->willReturn($paymentData); - $this->subjectReader->expects(self::once()) + ->willReturn($paymentDataMock); + $this->subjectReaderMock->expects(self::once()) ->method('readTransaction') ->with($response) ->willReturn($transaction); @@ -95,7 +94,7 @@ private function getPaymentDataObjectMock() $mock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); return $mock; } @@ -113,7 +112,7 @@ private function getBraintreeTransaction() 'cvvResponseCode' => 'M', 'processorAuthorizationCode' => 'W1V8XK', 'processorResponseCode' => '1000', - 'processorResponseText' => 'Approved' + 'processorResponseText' => 'Approved', ]; return Transaction::factory($attributes); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/RiskDataHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/RiskDataHandlerTest.php index 2365c396c2f4a..b86952ebf07a5 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/RiskDataHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/RiskDataHandlerTest.php @@ -6,7 +6,7 @@ namespace Magento\Braintree\Test\Unit\Gateway\Response; use Braintree\Transaction; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Response\RiskDataHandler; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; @@ -27,19 +27,19 @@ class RiskDataHandlerTest extends \PHPUnit\Framework\TestCase /** * @var SubjectReader|MockObject */ - private $subjectReader; + private $subjectReaderMock; /** - * Set up + * @inheritdoc */ protected function setUp() { - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) ->disableOriginalConstructor() ->setMethods(['readPayment', 'readTransaction']) ->getMock(); - $this->riskDataHandler = new RiskDataHandler($this->subjectReader); + $this->riskDataHandler = new RiskDataHandler($this->subjectReaderMock); } /** @@ -76,11 +76,11 @@ public function testHandle($riskDecision, $isFraud) 'payment' => $paymentDO, ]; - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readPayment') ->with($handlingSubject) ->willReturn($paymentDO); - $this->subjectReader->expects(static::once()) + $this->subjectReaderMock->expects(static::once()) ->method('readTransaction') ->with($response) ->willReturn($transaction); diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/ThreeDSecureDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/ThreeDSecureDetailsHandlerTest.php index 9ca9ca6aa07ae..e97eefc8a3444 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/ThreeDSecureDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/ThreeDSecureDetailsHandlerTest.php @@ -8,9 +8,8 @@ use Braintree\Transaction; use Magento\Braintree\Gateway\Response\ThreeDSecureDetailsHandler; use Magento\Payment\Gateway\Data\PaymentDataObject; -use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -29,7 +28,7 @@ class ThreeDSecureDetailsHandlerTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Sales\Model\Order\Payment|MockObject */ - private $payment; + private $paymentMock; /** * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject @@ -38,7 +37,7 @@ class ThreeDSecureDetailsHandlerTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->payment = $this->getMockBuilder(Payment::class) + $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->setMethods([ 'unsAdditionalInformation', @@ -74,10 +73,10 @@ public function testHandle() ->with($response) ->willReturn($transaction); - $this->payment->expects(static::at(1)) + $this->paymentMock->expects(static::at(1)) ->method('setAdditionalInformation') ->with('liabilityShifted', 'Yes'); - $this->payment->expects(static::at(2)) + $this->paymentMock->expects(static::at(2)) ->method('setAdditionalInformation') ->with('liabilityShiftPossible', 'Yes'); @@ -97,7 +96,7 @@ private function getPaymentDataObjectMock() $mock->expects(static::once()) ->method('getPayment') - ->willReturn($this->payment); + ->willReturn($this->paymentMock); return $mock; } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php index 3a2d2f7073573..6cbca707242f1 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Response; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Response\TransactionIdHandler; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php index fb8f507bf1214..c8ec52560be29 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php @@ -5,19 +5,23 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Response; +use Braintree\Result\Successful; use Braintree\Transaction; use Braintree\Transaction\CreditCardDetails; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Response\PaymentDetailsHandler; use Magento\Braintree\Gateway\Response\VaultDetailsHandler; -use Magento\Framework\DataObject; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Sales\Api\Data\OrderPaymentExtension; use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; -use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; -use Magento\Vault\Api\Data\PaymentTokenInterface; -use Magento\Vault\Model\CreditCardTokenFactory; +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; +use Magento\Vault\Model\PaymentToken; +use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -25,193 +29,143 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class VaultDetailsHandlerTest extends \PHPUnit\Framework\TestCase +class VaultDetailsHandlerTest extends TestCase { - const TRANSACTION_ID = '432erwwe'; + private static $transactionId = '432erwwe'; + + private static $token = 'rh3gd4'; /** - * @var \Magento\Braintree\Gateway\Response\PaymentDetailsHandler + * @var PaymentDetailsHandler */ private $paymentHandler; /** - * @var \Magento\Sales\Model\Order\Payment|MockObject + * @var Payment|MockObject */ private $payment; /** - * @var CreditCardTokenFactory|MockObject + * @var PaymentTokenFactoryInterface|MockObject */ private $paymentTokenFactory; /** - * @var PaymentTokenInterface|MockObject - */ - protected $paymentToken; - - /** - * @var \Magento\Sales\Api\Data\OrderPaymentExtension|MockObject + * @var OrderPaymentExtension|MockObject */ private $paymentExtension; /** - * @var \Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory|MockObject + * @var OrderPaymentExtensionInterfaceFactory|MockObject */ private $paymentExtensionFactory; - /** - * @var SubjectReader|MockObject - */ - private $subjectReader; - - /** - * @var Config|MockObject - */ - private $config; - protected function setUp() { - $this->paymentToken = $this->createMock(PaymentTokenInterface::class); - $this->paymentTokenFactory = $this->getMockBuilder(CreditCardTokenFactory::class) + $objectManager = new ObjectManager($this); + $paymentToken = $objectManager->getObject(PaymentToken::class); + $this->paymentTokenFactory = $this->getMockBuilder(PaymentTokenFactoryInterface::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->paymentTokenFactory->expects(self::once()) - ->method('create') - ->willReturn($this->paymentToken); + $this->paymentTokenFactory->method('create') + ->with(PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD) + ->willReturn($paymentToken); - $this->paymentExtension = $this->getMockBuilder(OrderPaymentExtensionInterface::class) - ->setMethods(['setVaultPaymentToken', 'getVaultPaymentToken']) - ->disableOriginalConstructor() - ->getMock(); - $this->paymentExtensionFactory = $this->getMockBuilder(OrderPaymentExtensionInterfaceFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->paymentExtensionFactory->expects(self::once()) - ->method('create') - ->willReturn($this->paymentExtension); + $this->initPaymentExtensionAttributesMock(); + $this->paymentExtension->method('setVaultPaymentToken') + ->with($paymentToken); + $this->paymentExtension->method('getVaultPaymentToken') + ->willReturn($paymentToken); $this->payment = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() - ->setMethods(['__wakeup']) - ->getMock(); - - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->getMock(); - - $mapperArray = [ - "american-express" => "AE", - "discover" => "DI", - "jcb" => "JCB", - "mastercard" => "MC", - "master-card" => "MC", - "visa" => "VI", - "maestro" => "MI", - "diners-club" => "DN", - "unionpay" => "CUP" - ]; - - $this->config = $this->getMockBuilder(Config::class) - ->setMethods(['getCctypesMapper']) - ->disableOriginalConstructor() + ->setMethods(['__wakeup', 'getExtensionAttributes']) ->getMock(); - $this->config->expects(self::once()) - ->method('getCctypesMapper') - ->willReturn($mapperArray); + $this->payment->expects(self::any())->method('getExtensionAttributes')->willReturn($this->paymentExtension); - $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); + $config = $this->getConfigMock(); $this->paymentHandler = new VaultDetailsHandler( $this->paymentTokenFactory, $this->paymentExtensionFactory, - $this->config, - $this->subjectReader, - $this->serializer + $config, + new SubjectReader(), + new Json() ); } - /** - * @covers \Magento\Braintree\Gateway\Response\VaultDetailsHandler::handle - */ public function testHandle() { - $this->paymentExtension->expects(self::once()) - ->method('setVaultPaymentToken') - ->with($this->paymentToken); - $this->paymentExtension->expects(self::once()) - ->method('getVaultPaymentToken') - ->willReturn($this->paymentToken); - $paymentData = $this->getPaymentDataObjectMock(); - $transaction = $this->getBraintreeTransaction(); $subject = ['payment' => $paymentData]; - $response = ['object' => $transaction]; - - $this->subjectReader->expects(self::once()) - ->method('readPayment') - ->with($subject) - ->willReturn($paymentData); - $this->subjectReader->expects(self::once()) - ->method('readTransaction') - ->with($response) - ->willReturn($transaction); - $this->paymentToken->expects(static::once()) - ->method('setGatewayToken') - ->with('rh3gd4'); - $this->paymentToken->expects(static::once()) - ->method('setExpiresAt') - ->with('2022-01-01 00:00:00'); + $response = ['object' => $this->getBraintreeTransaction()]; $this->paymentHandler->handle($subject, $response); - $this->assertSame($this->paymentToken, $this->payment->getExtensionAttributes()->getVaultPaymentToken()); + $paymentToken = $this->payment->getExtensionAttributes() + ->getVaultPaymentToken(); + + self::assertEquals(self::$token, $paymentToken->getGatewayToken()); + self::assertEquals('2022-01-01 00:00:00', $paymentToken->getExpiresAt()); + + $details = json_decode($paymentToken->getTokenDetails(), true); + self::assertEquals( + [ + 'type' => 'AE', + 'maskedCC' => 1231, + 'expirationDate' => '12/2021' + ], + $details + ); } /** - * Create mock for payment data object and order payment - * @return MockObject + * Creates mock for payment data object and order payment. + * + * @return PaymentDataObject|MockObject */ - private function getPaymentDataObjectMock() + private function getPaymentDataObjectMock(): PaymentDataObject { $mock = $this->getMockBuilder(PaymentDataObject::class) ->setMethods(['getPayment']) ->disableOriginalConstructor() ->getMock(); - $mock->expects($this->once()) - ->method('getPayment') + $mock->method('getPayment') ->willReturn($this->payment); return $mock; } /** - * Create Braintree transaction - * @return MockObject + * Creates Braintree transaction. + * + * @return Successful */ private function getBraintreeTransaction() { $attributes = [ - 'id' => self::TRANSACTION_ID, + 'id' => self::$transactionId, 'creditCardDetails' => $this->getCreditCardDetails() ]; $transaction = Transaction::factory($attributes); + $result = new Successful(['transaction' => $transaction]); - return $transaction; + return $result; } /** - * Create Braintree transaction - * @return \Braintree\Transaction\CreditCardDetails + * Creates Braintree transaction. + * + * @return CreditCardDetails */ - private function getCreditCardDetails() + private function getCreditCardDetails(): CreditCardDetails { $attributes = [ - 'token' => 'rh3gd4', + 'token' => self::$token, 'bin' => '5421', 'cardType' => 'American Express', 'expirationMonth' => 12, @@ -223,4 +177,54 @@ private function getCreditCardDetails() return $creditCardDetails; } + + /** + * Creates mock of config class. + * + * @return Config|MockObject + */ + private function getConfigMock(): Config + { + $mapperArray = [ + 'american-express' => 'AE', + 'discover' => 'DI', + 'jcb' => 'JCB', + 'mastercard' => 'MC', + 'master-card' => 'MC', + 'visa' => 'VI', + 'maestro' => 'MI', + 'diners-club' => 'DN', + 'unionpay' => 'CUP' + ]; + + $config = $this->getMockBuilder(Config::class) + ->setMethods(['getCctypesMapper']) + ->disableOriginalConstructor() + ->getMock(); + + $config->method('getCctypesMapper') + ->willReturn($mapperArray); + + return $config; + } + + /** + * Initializes payment extension attributes mocks. + * + * @return void + */ + private function initPaymentExtensionAttributesMock() + { + $this->paymentExtension = $this->getMockBuilder(OrderPaymentExtensionInterface::class) + ->setMethods(['setVaultPaymentToken', 'getVaultPaymentToken']) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentExtensionFactory = $this->getMockBuilder(OrderPaymentExtensionInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->paymentExtensionFactory->method('create') + ->willReturn($this->paymentExtension); + } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php index 398349a9692b7..a541b0115fe63 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Response/VoidHandlerTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Response; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Response\VoidHandler; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/SubjectReaderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/SubjectReaderTest.php new file mode 100644 index 0000000000000..fd524a10ba531 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/SubjectReaderTest.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Gateway; + +use Braintree\Result\Successful; +use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; + +/** + * Class SubjectReaderTest + */ +class SubjectReaderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->subjectReader = new SubjectReader(); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readCustomerId + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "customerId" field does not exists + * @return void + */ + public function testReadCustomerIdWithException(): void + { + $this->subjectReader->readCustomerId([]); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readCustomerId + * @return void + */ + public function testReadCustomerId(): void + { + $customerId = 1; + $this->assertEquals($customerId, $this->subjectReader->readCustomerId(['customer_id' => $customerId])); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readPublicHash + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "public_hash" field does not exists + * @return void + */ + public function testReadPublicHashWithException(): void + { + $this->subjectReader->readPublicHash([]); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readPublicHash + * @return void + */ + public function testReadPublicHash(): void + { + $hash = 'fj23djf2o1fd'; + $this->assertEquals($hash, $this->subjectReader->readPublicHash(['public_hash' => $hash])); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readPayPal + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Transaction has't paypal attribute + * @return void + */ + public function testReadPayPalWithException(): void + { + $transaction = Transaction::factory([ + 'id' => 'u38rf8kg6vn', + ]); + $this->subjectReader->readPayPal($transaction); + } + + /** + * @covers \Magento\Braintree\Gateway\SubjectReader::readPayPal + * @return void + */ + public function testReadPayPal(): void + { + $paypal = [ + 'paymentId' => '3ek7dk7fn0vi1', + 'payerEmail' => 'payer@example.com', + ]; + $transaction = Transaction::factory([ + 'id' => '4yr95vb', + 'paypal' => $paypal, + ]); + + $this->assertEquals($paypal, $this->subjectReader->readPayPal($transaction)); + } + + /** + * Checks a case when subject reader retrieves successful Braintree transaction. + * + * @return void + */ + public function testReadTransaction(): void + { + $transaction = Transaction::factory(['id' => 1]); + $response = [ + 'object' => new Successful($transaction, 'transaction'), + ]; + $actual = $this->subjectReader->readTransaction($response); + + $this->assertSame($transaction, $actual); + } + + /** + * Checks a case when subject reader retrieves invalid data instead transaction details. + * + * @param array $response + * @param string $expectedMessage + * @dataProvider invalidTransactionResponseDataProvider + * @expectedException \InvalidArgumentException + * @return void + */ + public function testReadTransactionWithInvalidResponse(array $response, string $expectedMessage): void + { + $this->expectExceptionMessage($expectedMessage); + $this->subjectReader->readTransaction($response); + } + + /** + * Gets list of variations with invalid subject data. + * + * @return array + */ + public function invalidTransactionResponseDataProvider(): array + { + $transaction = new \stdClass(); + $response = new \stdClass(); + $response->transaction = $transaction; + + return [ + [ + 'response' => [ + 'object' => [], + ], + 'expectedMessage' => 'Response object does not exist.', + ], + [ + 'response' => [ + 'object' => new \stdClass(), + ], + 'expectedMessage' => 'The object is not a class \Braintree\Transaction.', + ], + [ + 'response' => [ + 'object' => $response, + ], + 'expectedMessage' => 'The object is not a class \Braintree\Transaction.', + ], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/CancelResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/CancelResponseValidatorTest.php new file mode 100644 index 0000000000000..65386272fe511 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/CancelResponseValidatorTest.php @@ -0,0 +1,179 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Gateway\Validator; + +use Braintree\Result\Error; +use Magento\Braintree\Gateway\Validator\CancelResponseValidator; +use PHPUnit\Framework\TestCase; +use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests \Magento\Braintree\Gateway\Validator\CancelResponseValidator class. + */ +class CancelResponseValidatorTest extends TestCase +{ + /** + * @var CancelResponseValidator + */ + private $validator; + + /** + * @var GeneralResponseValidator|MockObject + */ + private $generalValidator; + + /** + * @var ResultInterfaceFactory|MockObject + */ + private $resultFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->generalValidator = $this->getMockBuilder(GeneralResponseValidator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultFactory = $this->getMockBuilder(ResultInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->validator = new CancelResponseValidator( + $this->resultFactory, + $this->generalValidator, + new SubjectReader() + ); + } + + /** + * Checks a case when response is successful and additional validation doesn't needed. + * + * @return void + */ + public function testValidateSuccessfulTransaction(): void + { + /** @var ResultInterface|MockObject $result */ + $result = $this->getMockForAbstractClass(ResultInterface::class); + $result->method('isValid')->willReturn(true); + $this->generalValidator->method('validate')->willReturn($result); + $actual = $this->validator->validate([]); + + $this->assertSame($result, $actual); + } + + /** + * Checks a case when response contains error related to expired authorization transaction and + * validator should return positive result. + * + * @return void + */ + public function testValidateExpiredTransaction(): void + { + /** @var ResultInterface|MockObject $result */ + $result = $this->getMockForAbstractClass(ResultInterface::class); + $result->method('isValid')->willReturn(false); + $this->generalValidator->method('validate')->willReturn($result); + + $expected = $this->getMockForAbstractClass(ResultInterface::class); + $expected->method('isValid')->willReturn(true); + $this->resultFactory->method('create') + ->with( + [ + 'isValid' => true, + 'failsDescription' => ['Transaction is cancelled offline.'], + 'errorCodes' => [] + ] + )->willReturn($expected); + + $errors = [ + 'errors' => [ + [ + 'code' => 91504, + 'message' => 'Transaction can only be voided if status is authorized.', + ], + ], + ]; + $buildSubject = [ + 'response' => [ + 'object' => new Error(['errors' => $errors]), + ], + ]; + + $actual = $this->validator->validate($buildSubject); + + $this->assertSame($expected, $actual); + } + + /** + * Checks a case when response contains multiple errors and validator should return negative result. + * + * @param array $responseErrors + * @return void + * @dataProvider getErrorsDataProvider + */ + public function testValidateWithMultipleErrors(array $responseErrors): void + { + /** @var ResultInterface|MockObject $result */ + $result = $this->getMockForAbstractClass(ResultInterface::class); + $result->method('isValid')->willReturn(false); + + $this->generalValidator->method('validate')->willReturn($result); + + $this->resultFactory->expects($this->never())->method('create'); + + $errors = [ + 'errors' => $responseErrors, + ]; + $buildSubject = [ + 'response' => [ + 'object' => new Error(['errors' => $errors]), + ] + ]; + + $actual = $this->validator->validate($buildSubject); + + $this->assertSame($result, $actual); + } + + /** + * Gets list of errors variations. + * + * @return array + */ + public function getErrorsDataProvider(): array + { + return [ + [ + 'errors' => [ + [ + 'code' => 91734, + 'message' => 'Credit card type is not accepted by this merchant account.', + ], + [ + 'code' => 91504, + 'message' => 'Transaction can only be voided if status is authorized.', + ], + ], + ], + [ + 'errors' => [ + [ + 'code' => 91734, + 'message' => 'Credit card type is not accepted by this merchant account.', + ], + ], + ], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php index 1a9e547e90636..4741a3ea38c6f 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php @@ -5,12 +5,14 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Braintree\Result\Error; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; +use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; use Magento\Framework\Phrase; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase { @@ -20,14 +22,9 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase private $responseValidator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ - private $resultInterfaceFactoryMock; - - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject - */ - private $subjectReaderMock; + private $resultInterfaceFactory; /** * Set up @@ -36,85 +33,105 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->resultInterfaceFactoryMock = $this->getMockBuilder( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory::class - )->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); $this->responseValidator = new GeneralResponseValidator( - $this->resultInterfaceFactoryMock, - $this->subjectReaderMock + $this->resultInterfaceFactory, + new SubjectReader(), + new ErrorCodeProvider() ); } /** - * Run test for validate method + * Checks a case when the validator processes successful and failed transactions. * * @param array $validationSubject * @param bool $isValid * @param Phrase[] $messages + * @param array $errorCodes * @return void * * @dataProvider dataProviderTestValidate */ - public function testValidate(array $validationSubject, $isValid, $messages) + public function testValidate(array $validationSubject, bool $isValid, $messages, array $errorCodes) { - /** @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject $resultMock */ - $resultMock = $this->createMock(ResultInterface::class); - - $this->subjectReaderMock->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); + $result = new Result($isValid, $messages); - $this->resultInterfaceFactoryMock->expects(self::once()) - ->method('create') + $this->resultInterfaceFactory->method('create') ->with([ 'isValid' => $isValid, - 'failsDescription' => $messages + 'failsDescription' => $messages, + 'errorCodes' => $errorCodes ]) - ->willReturn($resultMock); + ->willReturn($result); - $actualMock = $this->responseValidator->validate($validationSubject); + $actual = $this->responseValidator->validate($validationSubject); - self::assertEquals($resultMock, $actualMock); + self::assertEquals($result, $actual); } /** + * Gets variations for different type of response. + * * @return array */ public function dataProviderTestValidate() { - $successTrue = new \stdClass(); - $successTrue->success = true; + $successTransaction = new \stdClass(); + $successTransaction->success = true; - $successFalse = new \stdClass(); - $successFalse->success = false; + $failureTransaction = new \stdClass(); + $failureTransaction->success = false; + $failureTransaction->message = 'Transaction was failed.'; + + $errors = [ + 'errors' => [ + [ + 'code' => 81804, + 'attribute' => 'base', + 'message' => 'Cannot process transaction.' + ] + ] + ]; + $errorTransaction = new Error(['errors' => $errors]); return [ [ 'validationSubject' => [ 'response' => [ - 'object' => $successTrue + 'object' => $successTransaction ], ], 'isValid' => true, - [] + [], + 'errorCodes' => [] ], [ 'validationSubject' => [ 'response' => [ - 'object' => $successFalse + 'object' => $failureTransaction + ] + ], + 'isValid' => false, + [ + __('Transaction was failed.') + ], + 'errorCodes' => [] + ], + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $errorTransaction ] ], 'isValid' => false, [ __('Braintree error response.') - ] + ], + 'errorCodes' => ['81804'] ] ]; } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php index 294226b1656ec..530945c974ceb 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php @@ -5,15 +5,13 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; use Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Helper\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; -/** - * Class PaymentNonceResponseValidatorTest - */ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase { /** @@ -22,35 +20,24 @@ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase private $validator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ private $resultInterfaceFactory; - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject - */ - private $subjectReader; - protected function setUp() { $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->setMethods(['readResponseObject']) - ->getMock(); $this->validator = new PaymentNonceResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeProvider() ); } - /** - * @covers \Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator::validate - */ public function testFailedValidate() { $obj = new \stdClass(); @@ -61,23 +48,12 @@ public function testFailedValidate() ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => false, - 'failsDescription' => [ - __('Payment method nonce can\'t be retrieved.') - ] - ]) + $result = new Result(false, [__('Payment method nonce can\'t be retrieved.')]); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } public function testValidateSuccess() @@ -93,20 +69,11 @@ public function testValidateSuccess() ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => true, - 'failsDescription' => [] - ]) + $result = new Result(true); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php index aeb9b4a83077c..360e1ff0525b6 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php @@ -5,15 +5,16 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; +use Braintree\Result\Successful; use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; +use Magento\Braintree\Gateway\Validator\ResponseValidator; use Magento\Framework\Phrase; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\ResponseValidator; -use Magento\Braintree\Gateway\Helper\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Braintree\Result\Error; -use Braintree\Result\Successful; /** * Class ResponseValidatorTest @@ -30,11 +31,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase */ private $resultInterfaceFactory; - /** - * @var SubjectReader|MockObject - */ - private $subjectReader; - /** * Set up * @@ -46,13 +42,11 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->getMock(); $this->responseValidator = new ResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeProvider() ); } @@ -65,11 +59,6 @@ public function testValidateReadResponseException() 'response' => null ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -82,11 +71,6 @@ public function testValidateReadResponseObjectException() 'response' => ['object' => null] ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -103,19 +87,9 @@ public function testValidateReadResponseObjectException() public function testValidate(array $validationSubject, $isValid, $messages) { /** @var ResultInterface|MockObject $result */ - $result = $this->createMock(ResultInterface::class); - - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); - - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => $isValid, - 'failsDescription' => $messages - ]) + $result = new Result($isValid, $messages); + + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->responseValidator->validate($validationSubject); @@ -141,8 +115,6 @@ public function dataProviderTestValidate() $transactionDeclined->transaction = new \stdClass(); $transactionDeclined->transaction->status = Transaction::SETTLEMENT_DECLINED; - $errorResult = new Error(['errors' => []]); - return [ [ 'validationSubject' => [ @@ -175,18 +147,6 @@ public function dataProviderTestValidate() [ __('Wrong transaction status') ] - ], - [ - 'validationSubject' => [ - 'response' => [ - 'object' => $errorResult, - ] - ], - 'isValid' => false, - [ - __('Braintree error response.'), - __('Wrong transaction status') - ] ] ]; } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php index 9b80a2237a8fb..c82634d36db31 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php @@ -84,11 +84,11 @@ public function testGetCodeWithException() public function getCodeDataProvider() { return [ - ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'], - ['avsZip' => null, 'avsStreet' => 'M', 'expected' => 'U'], - ['avsZip' => 'M', 'avsStreet' => null, 'expected' => 'U'], - ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => 'U'], - ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => 'U'], + ['avsZip' => null, 'avsStreet' => null, 'expected' => ''], + ['avsZip' => null, 'avsStreet' => 'M', 'expected' => ''], + ['avsZip' => 'M', 'avsStreet' => null, 'expected' => ''], + ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => ''], + ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => ''], ['avsZip' => 'M', 'avsStreet' => 'M', 'expected' => 'Y'], ['avsZip' => 'N', 'avsStreet' => 'M', 'expected' => 'A'], ['avsZip' => 'M', 'avsStreet' => 'N', 'expected' => 'Z'], diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php new file mode 100644 index 0000000000000..2248aab1aad2e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\CreditCard; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Model\InstantPurchase\CreditCard\AvailabilityChecker; + +/** + * @covers \Magento\Braintree\Model\InstantPurchase\CreditCard\AvailabilityChecker + */ +class AvailabilityCheckerTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var AvailabilityChecker + */ + private $availabilityChecker; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->availabilityChecker = new AvailabilityChecker($this->configMock); + } + + /** + * Test isAvailable method + * + * @dataProvider isAvailableDataProvider + * + * @param bool $isVerify3DSecure + * @param bool $expected + * + * @return void + */ + public function testIsAvailable(bool $isVerify3DSecure, bool $expected) + { + $this->configMock->expects($this->once())->method('isVerify3DSecure')->willReturn($isVerify3DSecure); + $actual = $this->availabilityChecker->isAvailable(); + self::assertEquals($expected, $actual); + } + + /** + * Data provider for isAvailable method test + * + * @return array + */ + public function isAvailableDataProvider() + { + return [ + [true, false], + [false, true], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php new file mode 100644 index 0000000000000..a5c7cd743d85f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\CreditCard; + +use Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter as CreditCardTokenFormatter; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +class TokenFormatterTest extends TestCase +{ + /** + * @var PaymentTokenInterface|PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var CreditCardTokenFormatter + */ + private $creditCardTokenFormatter; + + /** + * @var array + */ + private $tokenDetails = [ + 'type' => 'visa', + 'maskedCC' => '1111************9999', + 'expirationDate' => '01-01-2020' + ]; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->paymentTokenMock = $this->getMockBuilder(PaymentTokenInterface::class) + ->getMockForAbstractClass(); + + $this->creditCardTokenFormatter = new CreditCardTokenFormatter(); + } + + /** + * Testing the payment format with a known credit card type + * + * @return void + */ + public function testFormatPaymentTokenWithKnownCardType() + { + $this->tokenDetails['type'] = key(CreditCardTokenFormatter::$baseCardTypes); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + reset(CreditCardTokenFormatter::$baseCardTypes), + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals( + $formattedString, + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock) + ); + } + + /** + * Testing the payment format with a unknown credit card type + * + * @return void + */ + public function testFormatPaymentTokenWithUnknownCardType() + { + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + $this->tokenDetails['type'], + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals( + $formattedString, + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock) + ); + } + + /** + * Testing the payment format with wrong card data + * + * @return void + */ + public function testFormatPaymentTokenWithWrongData() + { + unset($this->tokenDetails['type']); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + self::expectException('\InvalidArgumentException'); + + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php new file mode 100644 index 0000000000000..e4cd8fd58043b --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\PayPal; + +use Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter as PaypalTokenFormatter; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +class TokenFormatterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var PaymentTokenInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var PaypalTokenFormatter + */ + private $paypalTokenFormatter; + + /** + * @var array + */ + private $tokenDetails = [ + 'type' => 'visa', + 'maskedCC' => '4444************9999', + 'expirationDate' => '07-07-2025' + ]; + + /** + * Test setup + */ + protected function setUp() + { + $this->paymentTokenMock = $this->getMockBuilder(PaymentTokenInterface::class) + ->getMockForAbstractClass(); + + $this->paypalTokenFormatter = new PaypalTokenFormatter(); + } + + /** + * testFormatPaymentTokenWithKnownCardType + */ + public function testFormatPaymentTokenWithKnownCardType() + { + $this->tokenDetails['type'] = key(PaypalTokenFormatter::$baseCardTypes); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + reset(PaypalTokenFormatter::$baseCardTypes), + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals($formattedString, $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock)); + } + + /** + * testFormatPaymentTokenWithUnknownCardType + */ + public function testFormatPaymentTokenWithUnknownCardType() + { + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + $this->tokenDetails['type'], + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals($formattedString, $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock)); + } + + /** + * testFormatPaymentTokenWithWrongData + */ + public function testFormatPaymentTokenWithWrongData() + { + unset($this->tokenDetails['type']); + + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + self::expectException('\InvalidArgumentException'); + + $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php new file mode 100644 index 0000000000000..2631fcbe5f5b5 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase; + +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider; +use Magento\Payment\Gateway\Command\Result\ArrayResult; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +/** + * @covers \Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider + */ +class PaymentAdditionalInformationProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var PaymentAdditionalInformationProvider + */ + private $paymentAdditionalInformationProvider; + + /** + * @var GetPaymentNonceCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $getPaymentNonceCommandMock; + + /** + * @var PaymentTokenInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var ArrayResult|\PHPUnit_Framework_MockObject_MockObject + */ + private $arrayResultMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->getPaymentNonceCommandMock = $this->createMock(GetPaymentNonceCommand::class); + $this->paymentTokenMock = $this->createMock(PaymentTokenInterface::class); + $this->arrayResultMock = $this->createMock(ArrayResult::class); + $this->paymentAdditionalInformationProvider = new PaymentAdditionalInformationProvider( + $this->getPaymentNonceCommandMock + ); + } + + /** + * Test getAdditionalInformation method + * + * @return void + */ + public function testGetAdditionalInformation() + { + $customerId = 15; + $publicHash = '3n4b7sn48g'; + $paymentMethodNonce = 'test'; + + $this->paymentTokenMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->paymentTokenMock->expects($this->once())->method('getPublicHash')->willReturn($publicHash); + $this->getPaymentNonceCommandMock->expects($this->once())->method('execute')->with([ + PaymentTokenInterface::CUSTOMER_ID => $customerId, + PaymentTokenInterface::PUBLIC_HASH => $publicHash, + ])->willReturn($this->arrayResultMock); + $this->arrayResultMock->expects($this->once())->method('get') + ->willReturn(['paymentMethodNonce' => $paymentMethodNonce]); + + $expected = [ + 'payment_method_nonce' => $paymentMethodNonce, + ]; + $actual = $this->paymentAdditionalInformationProvider->getAdditionalInformation($this->paymentTokenMock); + self::assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php new file mode 100644 index 0000000000000..b6ef534c55c29 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model; + +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\LocaleResolver; +use Magento\Framework\Locale\ResolverInterface; + +/** + * @covers \Magento\Braintree\Model\LocaleResolver + */ +class LocaleResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var LocaleResolver + */ + private $localeResolver; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var ResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $resolverMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->resolverMock = $this->createMock(ResolverInterface::class); + $this->localeResolver = new LocaleResolver($this->resolverMock, $this->configMock); + } + + /** + * Test getDefaultLocalePath method + * + * @return void + */ + public function testGetDefaultLocalePath() + { + $expected = 'general/locale/code'; + $this->resolverMock->expects($this->once())->method('getDefaultLocalePath')->willReturn($expected); + $actual = $this->localeResolver->getDefaultLocalePath(); + self::assertEquals($expected, $actual); + } + + /** + * Test setDefaultLocale method + * + * @return void + */ + public function testSetDefaultLocale() + { + $defaultLocale = 'en_US'; + $this->resolverMock->expects($this->once())->method('setDefaultLocale')->with($defaultLocale); + $this->localeResolver->setDefaultLocale($defaultLocale); + } + + /** + * Test getDefaultLocale method + * + * @return void + */ + public function testGetDefaultLocale() + { + $expected = 'fr_FR'; + $this->resolverMock->expects($this->once())->method('getDefaultLocale')->willReturn($expected); + $actual = $this->localeResolver->getDefaultLocale(); + self::assertEquals($expected, $actual); + } + + /** + * Test setLocale method + * + * @return void + */ + public function testSetLocale() + { + $locale = 'en_GB'; + $this->resolverMock->expects($this->once())->method('setLocale')->with($locale); + $this->localeResolver->setLocale($locale); + } + + /** + * Test getLocale method + * + * @return void + */ + public function testGetLocale() + { + $locale = 'en_TEST'; + $allowedLocales = 'en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL'; + $this->resolverMock->expects($this->once())->method('getLocale')->willReturn($locale); + $this->configMock->expects($this->once())->method('getValue')->with('supported_locales') + ->willReturn($allowedLocales); + + $expected = 'en_US'; + $actual = $this->localeResolver->getLocale(); + self::assertEquals($expected, $actual); + } + + /** + * Test emulate method + * + * @return void + */ + public function testEmulate() + { + $scopeId = 12; + $this->resolverMock->expects($this->once())->method('emulate')->with($scopeId); + $this->localeResolver->emulate($scopeId); + } + + /** + * Test revert method + * + * @return void + */ + public function testRevert() + { + $this->resolverMock->expects($this->once())->method('revert'); + $this->localeResolver->revert(); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php index 39863e6561c43..62452228b6186 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php @@ -13,6 +13,7 @@ use Magento\Braintree\Observer\DataAssignObserver; use Magento\Braintree\Gateway\Config\PayPal\Config; use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater; +use Magento\Quote\Api\Data\CartExtensionInterface; /** * Class QuoteUpdaterTest @@ -50,6 +51,9 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase */ private $quoteUpdater; + /** + * @return void + */ protected function setUp() { $this->configMock = $this->getMockBuilder(Config::class) @@ -98,6 +102,10 @@ protected function setUp() ); } + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecute() { $details = $this->getDetails(); @@ -120,6 +128,9 @@ public function testExecute() $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quoteMock); } + /** + * @return void + */ private function disabledQuoteAddressValidationStep() { $this->billingAddressMock->expects(self::once()) @@ -281,7 +292,7 @@ private function updateQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quote */ private function getQuoteMock() { - return $this->getMockBuilder(Quote::class) + $quoteMock = $this->getMockBuilder(Quote::class) ->setMethods( [ 'getIsVirtual', @@ -291,9 +302,21 @@ private function getQuoteMock() 'collectTotals', 'getShippingAddress', 'getBillingAddress', + 'getExtensionAttributes' ] )->disableOriginalConstructor() ->getMock(); + + $cartExtensionMock = $this->getMockBuilder(CartExtensionInterface::class) + ->setMethods(['setShippingAssignments']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $quoteMock->expects(self::any()) + ->method('getExtensionAttributes') + ->willReturn($cartExtensionMock); + + return $quoteMock; } /** diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php index 5c28b94ac9811..372415d3530c0 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php @@ -34,10 +34,9 @@ public function __get($name) { if (array_key_exists($name, $this->_attributes)) { return $this->_attributes[$name]; - } else { - trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE); - return null; } + trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE); + return null; } /** diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Report/TransactionsCollectionTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Report/TransactionsCollectionTest.php index e43e67c18744f..f33af81b19746 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Report/TransactionsCollectionTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Report/TransactionsCollectionTest.php @@ -6,10 +6,12 @@ namespace Magento\Braintree\Test\Unit\Model\Report; use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Braintree\Model\Report\FilterMapper; use Magento\Braintree\Model\Report\TransactionsCollection; use Magento\Framework\Api\Search\DocumentInterface; use Magento\Framework\Data\Collection\EntityFactoryInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class TransactionsCollectionTest @@ -19,22 +21,27 @@ class TransactionsCollectionTest extends \PHPUnit\Framework\TestCase { /** - * @var BraintreeAdapter|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapter|MockObject */ private $braintreeAdapterMock; /** - * @var EntityFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var BraintreeAdapterFactory|MockObject + */ + private $adapterFactoryMock; + + /** + * @var EntityFactoryInterface|MockObject */ private $entityFactoryMock; /** - * @var FilterMapper|\PHPUnit_Framework_MockObject_MockObject + * @var FilterMapper|MockObject */ private $filterMapperMock; /** - * @var DocumentInterface|\PHPUnit_Framework_MockObject_MockObject + * @var DocumentInterface|MockObject */ private $transactionMapMock; @@ -61,6 +68,11 @@ protected function setUp() ->setMethods(['search']) ->disableOriginalConstructor() ->getMock(); + $this->adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapterFactoryMock->method('create') + ->willReturn($this->braintreeAdapterMock); } /** @@ -82,7 +94,7 @@ public function testGetItems() $collection = new TransactionsCollection( $this->entityFactoryMock, - $this->braintreeAdapterMock, + $this->adapterFactoryMock, $this->filterMapperMock ); @@ -111,7 +123,7 @@ public function testGetItemsEmptyCollection() $collection = new TransactionsCollection( $this->entityFactoryMock, - $this->braintreeAdapterMock, + $this->adapterFactoryMock, $this->filterMapperMock ); @@ -125,7 +137,7 @@ public function testGetItemsEmptyCollection() */ public function testGetItemsWithLimit() { - $transations = range(1, TransactionsCollection::TRANSACTION_MAXIMUM_COUNT + 10); + $transactions = range(1, TransactionsCollection::TRANSACTION_MAXIMUM_COUNT + 10); $this->filterMapperMock->expects($this->once()) ->method('getFilter') @@ -133,7 +145,7 @@ public function testGetItemsWithLimit() $this->braintreeAdapterMock->expects($this->once()) ->method('search') - ->willReturn($transations); + ->willReturn($transactions); $this->entityFactoryMock->expects($this->exactly(TransactionsCollection::TRANSACTION_MAXIMUM_COUNT)) ->method('create') @@ -141,7 +153,7 @@ public function testGetItemsWithLimit() $collection = new TransactionsCollection( $this->entityFactoryMock, - $this->braintreeAdapterMock, + $this->adapterFactoryMock, $this->filterMapperMock ); $collection->setPageSize(TransactionsCollection::TRANSACTION_MAXIMUM_COUNT); @@ -157,7 +169,7 @@ public function testGetItemsWithLimit() */ public function testGetItemsWithNullLimit() { - $transations = range(1, TransactionsCollection::TRANSACTION_MAXIMUM_COUNT + 10); + $transactions = range(1, TransactionsCollection::TRANSACTION_MAXIMUM_COUNT + 10); $this->filterMapperMock->expects($this->once()) ->method('getFilter') @@ -165,7 +177,7 @@ public function testGetItemsWithNullLimit() $this->braintreeAdapterMock->expects($this->once()) ->method('search') - ->willReturn($transations); + ->willReturn($transactions); $this->entityFactoryMock->expects($this->exactly(TransactionsCollection::TRANSACTION_MAXIMUM_COUNT)) ->method('create') @@ -173,7 +185,7 @@ public function testGetItemsWithNullLimit() $collection = new TransactionsCollection( $this->entityFactoryMock, - $this->braintreeAdapterMock, + $this->adapterFactoryMock, $this->filterMapperMock ); $collection->setPageSize(null); @@ -198,7 +210,7 @@ public function testAddToFilter($field, $condition, $filterMapperCall, $expected $collection = new TransactionsCollection( $this->entityFactoryMock, - $this->braintreeAdapterMock, + $this->adapterFactoryMock, $this->filterMapperMock ); diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php index 6c85ae68eb7af..24bc4eae960be 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -7,7 +7,9 @@ use Magento\Braintree\Gateway\Config\Config; use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Customer\Model\Session; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -31,6 +33,11 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase */ private $braintreeAdapter; + /** + * @var Session|MockObject + */ + private $session; + /** * @var ConfigProvider */ @@ -45,10 +52,24 @@ protected function setUp() $this->braintreeAdapter = $this->getMockBuilder(BraintreeAdapter::class) ->disableOriginalConstructor() ->getMock(); + /** @var BraintreeAdapterFactory|MockObject $adapterFactoryMock */ + $adapterFactoryMock = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactoryMock->method('create') + ->willReturn($this->braintreeAdapter); + + $this->session = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->setMethods(['getStoreId']) + ->getMock(); + $this->session->method('getStoreId') + ->willReturn(null); $this->configProvider = new ConfigProvider( $this->config, - $this->braintreeAdapter + $adapterFactoryMock, + $this->session ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php index 22f7f46bd98f1..42469fe0faf45 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php @@ -77,6 +77,9 @@ public function testGetConfig($expected) 'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url' ]); + $this->config->method('isRequiredBillingAddress') + ->willReturn(1); + self::assertEquals($expected, $this->configProvider->getConfig()); } @@ -101,7 +104,8 @@ public function getConfigDataProvider() 'skipOrderReview' => false, 'paymentIcon' => [ 'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url' - ] + ], + 'isRequiredBillingAddress' => true ] ] ] diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index c14addf550dfb..5af56a2afd3fe 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -5,32 +5,32 @@ "sort-packages": true }, "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "braintree/braintree_php": "3.22.0", - "magento/framework": "100.3.*", + "php": "~7.1.3||~7.2.0", + "braintree/braintree_php": "3.35.0", + "magento/framework": "*", "magento/magento-composer-installer": "*", - "magento/module-catalog": "101.2.*", - "magento/module-backend": "100.3.*", - "magento/module-checkout": "100.3.*", - "magento/module-config": "100.3.*", - "magento/module-customer": "100.3.*", - "magento/module-directory": "100.3.*", - "magento/module-instant-purchase": "100.3.*", - "magento/module-payment": "100.3.*", - "magento/module-paypal": "100.3.*", - "magento/module-quote": "100.3.*", - "magento/module-sales": "100.3.*", - "magento/module-ui": "100.3.*", - "magento/module-vault": "100.3.*" + "magento/module-catalog": "*", + "magento/module-backend": "*", + "magento/module-checkout": "*", + "magento/module-config": "*", + "magento/module-customer": "*", + "magento/module-directory": "*", + "magento/module-instant-purchase": "*", + "magento/module-payment": "*", + "magento/module-paypal": "*", + "magento/module-quote": "*", + "magento/module-sales": "*", + "magento/module-ui": "*", + "magento/module-vault": "*" }, "suggest": { - "magento/module-checkout-agreements": "100.3.*", - "magento/module-theme": "100.3.*" + "magento/module-checkout-agreements": "*", + "magento/module-theme": "*" }, "type": "magento2-module", - "version": "100.3.0-dev", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Braintree/etc/acl.xml b/app/code/Magento/Braintree/etc/acl.xml index a188586cc0a26..066ccc818d4e6 100644 --- a/app/code/Magento/Braintree/etc/acl.xml +++ b/app/code/Magento/Braintree/etc/acl.xml @@ -11,7 +11,16 @@ <resource id="Magento_Backend::admin"> <resource id="Magento_Reports::report"> <resource id="Magento_Reports::salesroot"> - <resource id="Magento_Braintree::settlement_report" title="Braintree Settlement" sortOrder="80" /> + <resource id="Magento_Braintree::settlement_report" title="Braintree Settlement" translate="title" sortOrder="80" /> + </resource> + </resource> + <resource id="Magento_Sales::sales"> + <resource id="Magento_Sales::sales_operation"> + <resource id="Magento_Sales::sales_order"> + <resource id="Magento_Sales::actions"> + <resource id="Magento_Braintree::get_client_token" title="Get Client Token Braintree" sortOrder="170" /> + </resource> + </resource> </resource> </resource> </resource> diff --git a/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml new file mode 100644 index 0000000000000..611f9372518fc --- /dev/null +++ b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/error_mapping.xsd"> + <message_list> + <message code="81509" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91504" translate="true">Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending.</message> + <message code="91505" translate="true">Credit transactions cannot be refunded.</message> + <message code="91506" translate="true">Cannot refund a transaction unless it is settled.</message> + <message code="91507" translate="true">Cannot submit for settlement unless status is authorized.</message> + <message code="91511" translate="true">Customer does not have any credit cards.</message> + <message code="91512" translate="true">Transaction has already been completely refunded.</message> + <message code="91517" translate="true">Payment instrument type is not accepted by this merchant account.</message> + <message code="91519" translate="true">Processor authorization code cannot be set unless for a voice authorization.</message> + <message code="91521" translate="true">Refund amount is too large.</message> + <message code="91522" translate="true">Settlement amount is too large.</message> + <message code="91530" translate="true">Cannot provide a billing address unless also providing a credit card.</message> + <message code="91538" translate="true">Cannot refund a transaction with a suspended merchant account.</message> + <message code="91547" translate="true">Merchant account does not support refunds.</message> + <message code="91574" translate="true">Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled.</message> + <message code="91576" translate="true">PayPal is not enabled for your merchant account.</message> + <message code="915102" translate="true">Partial settlements are not supported by this processor.</message> + <message code="915103" translate="true">Cannot submit for partial settlement.</message> + <message code="915148" translate="true">Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.</message> + <message code="915149" translate="true">Too many concurrent attempts to refund this transaction. Try again later.</message> + <message code="915151" translate="true">Too many concurrent attempts to void this transaction. Try again later.</message> + </message_list> +</mapping> diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index d0469ded83b67..9de1ad48d2261 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -28,6 +28,8 @@ <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> <item name="vault" xsi:type="string">Magento\Braintree\Gateway\Request\VaultDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> + <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -39,10 +41,25 @@ <item name="channel" xsi:type="string">Magento\Braintree\Gateway\Request\ChannelDataBuilder</item> <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> + <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> + <!-- Braintree commands --> + <type name="BraintreeVoidCommand"> + <arguments> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> + </arguments> + </type> + <type name="BraintreeRefundCommand"> + <arguments> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> + </arguments> + </type> + <!-- END Braintree commands --> + <type name="Magento\Vault\Model\Ui\Adminhtml\TokensConfigProvider"> <arguments> <argument name="tokenUiComponentProviders" xsi:type="array"> diff --git a/app/code/Magento/Braintree/etc/adminhtml/menu.xml b/app/code/Magento/Braintree/etc/adminhtml/menu.xml index 590d5b3dce008..ce4dd4844f3bc 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/menu.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/menu.xml @@ -10,6 +10,7 @@ <add id="Magento_Braintree::settlement_report" title="Braintree Settlement" + translate="title" module="Magento_Braintree" sortOrder="80" parent="Magento_Reports::report_salesroot" diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index f46da366b64a3..5215dbc00b7ef 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -44,7 +44,7 @@ <comment>http://docs.magento.com/m2/ce/user_guide/payment/braintree.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> - <group id="braintree_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="5"> + <group id="braintree_required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="5"> <label>Basic Braintree Settings</label> <attribute type="expanded">1</attribute> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> @@ -77,25 +77,25 @@ <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> </group> - <group id="braintree_advanced" translate="label" showInDefault="1" showInWebsite="1" sortOrder="20"> + <group id="braintree_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="20"> <label>Advanced Braintree Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> <field id="braintree_cc_vault_title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Vault Title</label> <config_path>payment/braintree_cc_vault/title</config_path> </field> - <field id="merchant_account_id" translate="label" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_account_id" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Merchant Account ID</label> <comment>If you don't specify the merchant account to use to process a transaction, Braintree will process it using your default merchant account.</comment> <config_path>payment/braintree/merchant_account_id</config_path> </field> - <field id="fraudprotection" translate="label" type="select" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="fraudprotection" translate="label comment" type="select" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Advanced Fraud Protection</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable Advanced Fraud Protection in Your Braintree Account in Settings/Processing Section</comment> <config_path>payment/braintree/fraudprotection</config_path> </field> - <field id="kount_id" translate="label" sortOrder="35" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="kount_id" translate="label comment" sortOrder="35" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Kount Merchant ID</label> <comment><![CDATA[Used for direct fraud tool integration. Make sure you also contact <a href="mailto:accounts@braintreepayments.com">accounts@braintreepayments.com</a> to setup your Kount account.]]></comment> <depends> @@ -108,7 +108,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/debug</config_path> </field> - <field id="useccv" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="useccv" translate="label comment" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> <label>CVV Verification</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable AVS and/or CVV in Your Braintree Account in Settings/Processing Section.</comment> @@ -125,7 +125,7 @@ <config_path>payment/braintree/sort_order</config_path> </field> </group> - <group id="braintree_country_specific" translate="label" showInDefault="1" showInWebsite="1" sortOrder="30"> + <group id="braintree_country_specific" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="30"> <label>Country Specific Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> <field id="allowspecific" translate="label" type="allowspecific" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0"> @@ -146,10 +146,10 @@ <config_path>payment/braintree/countrycreditcard</config_path> </field> </group> - <group id="braintree_paypal" translate="label" showInDefault="1" showInWebsite="1" sortOrder="40"> + <group id="braintree_paypal" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> <label>PayPal through Braintree</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="title" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Title</label> <config_path>payment/braintree_paypal/title</config_path> <comment>It is recommended to set this value to "PayPal" per store views.</comment> @@ -187,7 +187,7 @@ <can_be_empty>1</can_be_empty> <config_path>payment/braintree_paypal/specificcountry</config_path> </field> - <field id="require_billing_address" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="require_billing_address" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Require Customer's Billing Address</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/require_billing_address</config_path> @@ -203,7 +203,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/debug</config_path> </field> - <field id="display_on_shopping_cart" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="display_on_shopping_cart" translate="label comment" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Display on Shopping Cart</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/display_on_shopping_cart</config_path> @@ -215,7 +215,7 @@ <config_path>payment/braintree_paypal/skip_order_review</config_path> </field> </group> - <group id="braintree_3dsecure" translate="label" showInDefault="1" showInWebsite="1" sortOrder="41"> + <group id="braintree_3dsecure" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="41"> <label>3D Secure Verification Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> <field id="verify_3dsecure" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> @@ -239,14 +239,14 @@ <config_path>payment/braintree/verify_specific_countries</config_path> </field> </group> - <group id="braintree_dynamic_descriptor" translate="label" showInDefault="1" showInWebsite="1" sortOrder="50"> + <group id="braintree_dynamic_descriptor" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> <label>Dynamic Descriptors</label> <comment><![CDATA[Dynamic descriptors are sent on a per-transaction basis and define what will appear on your customers credit card statements for a specific purchase. The clearer the description of your product, the less likely customers will issue chargebacks due to confusion or non-recognition. Dynamic descriptors are not enabled on all accounts by default. If you receive a validation error of 92203 or if your dynamic descriptors are not displaying as expected, please <a href="mailto:support@getbraintree.com">Braintree Support</a>.]]></comment> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="name" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="name" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Name</label> <config_path>payment/braintree/descriptor_name</config_path> <comment> @@ -254,14 +254,14 @@ and the product descriptor can be up to 18, 14, or 9 characters respectively (with an * in between for a total descriptor name of 22 characters). </comment> </field> - <field id="phone" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="phone" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Phone</label> <config_path>payment/braintree/descriptor_phone</config_path> <comment> The value in the phone number field of a customer's statement. Phone must be 10-14 characters and can only contain numbers, dashes, parentheses and periods. </comment> </field> - <field id="url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="url" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>URL</label> <config_path>payment/braintree/descriptor_url</config_path> <comment> diff --git a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml new file mode 100644 index 0000000000000..81da0a252e567 --- /dev/null +++ b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml @@ -0,0 +1,64 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/error_mapping.xsd"> + <message_list> + <message code="81703" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="81706" translate="true">CVV is required.</message> + <message code="81707" translate="true">CVV must be 4 digits for American Express and 3 digits for other card types.</message> + <message code="81709" translate="true">Expiration date is required.</message> + <message code="81710" translate="true">Expiration date is invalid.</message> + <message code="81711" translate="true">Expiration year is invalid. It must be between 1975 and 2201.</message> + <message code="81712" translate="true">Expiration month is invalid.</message> + <message code="81713" translate="true">Expiration year is invalid.</message> + <message code="81714" translate="true">Credit card number is required.</message> + <message code="81715" translate="true">Credit card number is invalid.</message> + <message code="81716" translate="true">Credit card number must be 12-19 digits.</message> + <message code="81723" translate="true">Cardholder name is too long.</message> + <message code="81736" translate="true">CVV verification failed.</message> + <message code="81737" translate="true">Postal code verification failed.</message> + <message code="81750" translate="true">Credit card number is prohibited.</message> + <message code="81801" translate="true">Addresses must have at least one field filled in.</message> + <message code="81802" translate="true">Company is too long.</message> + <message code="81804" translate="true">Extended address is too long.</message> + <message code="81805" translate="true">First name is too long.</message> + <message code="81806" translate="true">Last name is too long.</message> + <message code="81807" translate="true">Locality is too long.</message> + <message code="81808" translate="true">Postal code is required.</message> + <message code="81809" translate="true">Postal code may contain no more than 9 letter or number characters.</message> + <message code="81810" translate="true">Region is too long.</message> + <message code="81811" translate="true">Street address is required.</message> + <message code="81812" translate="true">Street address is too long.</message> + <message code="81813" translate="true">Postal code can only contain letters, numbers, spaces, and hyphens.</message> + <message code="81827" translate="true">US state codes must be two characters to meet PayPal Seller Protection requirements.</message> + <message code="82901" translate="true">Incomplete PayPal account information.</message> + <message code="82903" translate="true">Invalid PayPal account information.</message> + <message code="82904" translate="true">PayPal Accounts are not accepted by this merchant account.</message> + <message code="91726" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91734" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91744" translate="true">Billing address format is invalid.</message> + <message code="91803" translate="true">Country name is not an accepted country.</message> + <message code="91814" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91815" translate="true">Provided country information is inconsistent.</message> + <message code="91816" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91817" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91818" translate="true">Customer has already reached the maximum of 50 addresses.</message> + <message code="91819" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91820" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91821" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91822" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91823" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91824" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91825" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91826" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91828" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="92910" translate="true">Error communicating with PayPal.</message> + <message code="92911" translate="true">PayPal authentication expired.</message> + <message code="92916" translate="true">Error executing PayPal order.</message> + <message code="92917" translate="true">Error executing PayPal billing agreement.</message> + </message_list> +</mapping> diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index 6bc2fb1735b52..a830c29368755 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -82,8 +82,7 @@ <title>Stored Accounts (Braintree PayPal) 1 - Magento\Braintree\Model\InstantPurchase\CreditCard\AvailabilityChecker - Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter + Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 2a451e132eab0..67c90e6991e28 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -133,8 +133,8 @@ BraintreeVaultCaptureCommand BraintreeVoidCommand BraintreeRefundCommand - BraintreeVoidCommand - BraintreeVoidCommand + Magento\Braintree\Gateway\CancelCommand + Magento\Braintree\Gateway\CancelCommand @@ -150,7 +150,7 @@ BraintreeVaultCaptureCommand BraintreeVoidCommand BraintreeRefundCommand - BraintreeVoidCommand + Magento\Braintree\Gateway\CancelCommand @@ -193,6 +193,23 @@ + + + braintree_error_mapping.xml + + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualConfigReader + braintree_error_mapper + + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualMappingData + + + @@ -201,6 +218,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale BraintreeAuthorizationHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -214,6 +232,8 @@ Magento\Braintree\Gateway\Request\ThreeDSecureDataBuilder Magento\Braintree\Gateway\Request\KountPaymentDataBuilder Magento\Braintree\Gateway\Request\DescriptorDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder @@ -239,12 +259,14 @@ Magento\Braintree\Gateway\Http\Client\TransactionSubmitForSettlement Magento\Braintree\Gateway\Response\TransactionIdHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper Magento\Braintree\Gateway\Request\CaptureDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder @@ -256,6 +278,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale BraintreeVaultResponseHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -268,6 +291,8 @@ Magento\Braintree\Gateway\Request\ThreeDSecureDataBuilder Magento\Braintree\Gateway\Request\KountPaymentDataBuilder Magento\Braintree\Gateway\Request\DescriptorDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder @@ -293,6 +318,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale Magento\Braintree\Gateway\Response\TransactionIdHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -300,6 +326,8 @@ Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder Magento\Braintree\Gateway\Request\SettlementDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder @@ -321,6 +349,8 @@ Magento\Braintree\Gateway\Request\PayPal\VaultDataBuilder Magento\Braintree\Gateway\Request\PayPal\DeviceDataBuilder Magento\Braintree\Gateway\Request\DescriptorDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder @@ -353,6 +383,8 @@ Magento\Braintree\Gateway\Request\ChannelDataBuilder Magento\Braintree\Gateway\Request\AddressDataBuilder Magento\Braintree\Gateway\Request\DescriptorDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder @@ -375,7 +407,7 @@ - Magento\Vault\Model\CreditCardTokenFactory + Magento\Vault\Api\Data\PaymentTokenFactoryInterface @@ -420,7 +452,7 @@ - Magento\Vault\Model\AccountPaymentTokenFactory + Magento\Vault\Api\Data\PaymentTokenFactoryInterface @@ -463,23 +495,49 @@ Magento\Braintree\Gateway\Http\Client\TransactionVoid - Magento\Braintree\Gateway\Request\VoidDataBuilder + BraintreeVoidRequestBuilder Magento\Braintree\Gateway\Response\VoidHandler Magento\Braintree\Gateway\Validator\GeneralResponseValidator Magento\Braintree\Gateway\Http\TransferFactory + + + + Magento\Braintree\Gateway\Request\VoidDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + + + + + + + + Magento\Braintree\Gateway\Response\CancelDetailsHandler + Magento\Braintree\Gateway\Validator\CancelResponseValidator + + + Magento\Braintree\Gateway\Http\Client\TransactionRefund - Magento\Braintree\Gateway\Request\RefundDataBuilder + BraintreeRefundBuilder Magento\Braintree\Gateway\Validator\GeneralResponseValidator Magento\Braintree\Gateway\Response\RefundHandler Magento\Braintree\Gateway\Http\TransferFactory + + + + Magento\Braintree\Gateway\Request\RefundDataBuilder + Magento\Braintree\Gateway\Request\StoreConfigBuilder + + + + @@ -494,7 +552,7 @@ - + diff --git a/app/code/Magento/Braintree/etc/module.xml b/app/code/Magento/Braintree/etc/module.xml index e3415c4935ff6..8be79268e7b58 100644 --- a/app/code/Magento/Braintree/etc/module.xml +++ b/app/code/Magento/Braintree/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index 116f459a1c1c8..6bf677151ed0d 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -129,3 +129,66 @@ Amount,Amount "Refund Ids","Refund Ids" "Settlement Batch ID","Settlement Batch ID" Currency,Currency +"Addresses must have at least one field filled in.","Addresses must have at least one field filled in." +"Company is too long.","Company is too long." +"Extended address is too long.","Extended address is too long." +"First name is too long.","First name is too long." +"Last name is too long.","Last name is too long." +"Locality is too long.","Locality is too long." +"Postal code can only contain letters, numbers, spaces, and hyphens.","Postal code can only contain letters, numbers, spaces, and hyphens." +"Postal code is required.","Postal code is required." +"Postal code may contain no more than 9 letter or number characters.","Postal code may contain no more than 9 letter or number characters." +"Region is too long.","Region is too long." +"Street address is required.","Street address is required." +"Street address is too long.","Street address is too long." +"US state codes must be two characters to meet PayPal Seller Protection requirements.","US state codes must be two characters to meet PayPal Seller Protection requirements." +"Country name is not an accepted country.","Country name is not an accepted country." +"Provided country information is inconsistent.","Provided country information is inconsistent." +"Country code is not accepted. Please contact the store administrator.","Country code is not accepted. Please contact the store administrator." +"Customer has already reached the maximum of 50 addresses.","Customer has already reached the maximum of 50 addresses." +"Address is invalid. Please contact the store administrator.","Address is invalid. Please contact the store administrator." +"Address is invalid.","Address is invalid." +"Billing address format is invalid.","Billing address format is invalid." +"Cardholder name is too long.","Cardholder name is too long." +"Credit card type is not accepted by this merchant account.","Credit card type is not accepted by this merchant account." +"CVV is required.","CVV is required." +"CVV must be 4 digits for American Express and 3 digits for other card types.","CVV must be 4 digits for American Express and 3 digits for other card types." +"Expiration date is required.","Expiration date is required." +"Expiration date is invalid.","Expiration date is invalid." +"Expiration year is invalid. It must be between 1975 and 2201.","Expiration year is invalid. It must be between 1975 and 2201." +"Expiration month is invalid.","Expiration month is invalid." +"Expiration year is invalid.","Expiration year is invalid." +"Credit card number is required.","Credit card number is required." +"Credit card number is invalid.","Credit card number is invalid." +"Credit card number must be 12-19 digits.","Credit card number must be 12-19 digits." +"CVV verification failed.","CVV verification failed." +"Postal code verification failed.","Postal code verification failed." +"Credit card number is prohibited.","Credit card number is prohibited." +"Incomplete PayPal account information.","Incomplete PayPal account information." +"Invalid PayPal account information.","Invalid PayPal account information." +"PayPal Accounts are not accepted by this merchant account.","PayPal Accounts are not accepted by this merchant account." +"Error communicating with PayPal.","Error communicating with PayPal." +"PayPal authentication expired.","PayPal authentication expired." +"Error executing PayPal order.","Error executing PayPal order." +"Error executing PayPal billing agreement.","Error executing PayPal billing agreement." +"Cannot provide a billing address unless also providing a credit card.","Cannot provide a billing address unless also providing a credit card." +"Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending.","Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending." +"Credit transactions cannot be refunded.","Credit transactions cannot be refunded." +"Cannot refund a transaction unless it is settled.","Cannot refund a transaction unless it is settled." +"Cannot submit for settlement unless status is authorized.","Cannot submit for settlement unless status is authorized." +"Customer does not have any credit cards.","Customer does not have any credit cards." +"Transaction has already been completely refunded.","Transaction has already been completely refunded." +"Payment instrument type is not accepted by this merchant account.","Payment instrument type is not accepted by this merchant account." +"Processor authorization code cannot be set unless for a voice authorization.","Processor authorization code cannot be set unless for a voice authorization." +"Refund amount is too large.","Refund amount is too large." +"Settlement amount is too large.","Settlement amount is too large." +"Cannot refund a transaction with a suspended merchant account.","Cannot refund a transaction with a suspended merchant account." +"Merchant account does not support refunds.","Merchant account does not support refunds." +"PayPal is not enabled for your merchant account.","PayPal is not enabled for your merchant account." +"Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled.","Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled." +"Cannot submit for partial settlement.","Cannot submit for partial settlement." +"Partial settlements are not supported by this processor.","Partial settlements are not supported by this processor." +"Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.","Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending." +"Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later." +"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later." +"Braintree Settlement","Braintree Settlement" 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 13249cd8e0e80..535a5a852fe70 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml @@ -84,7 +84,7 @@ $ccType = $block->getInfoData('cc_type'); name="payment[is_active_payment_token_enabler]" class="admin__control-checkbox"/> diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js index c8aaa65cebb71..ab01565d7f1e5 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js @@ -24,6 +24,7 @@ define([ scriptLoaded: false, braintree: null, selectedCardType: null, + checkout: null, imports: { onActiveChange: 'active' } @@ -116,7 +117,7 @@ define([ }, /** - * Setup Braintree SDK + * Retrieves client token and setup Braintree SDK */ initBraintree: function () { var self = this; @@ -124,35 +125,14 @@ define([ try { $('body').trigger('processStart'); - self.braintree.setup(self.clientToken, 'custom', { - id: self.selector, - hostedFields: self.getHostedFields(), - - /** - * Triggered when sdk was loaded - */ - onReady: function () { - $('body').trigger('processStop'); - }, - - /** - * Callback for success response - * @param {Object} response - */ - onPaymentMethodReceived: function (response) { - if (self.validateCardType()) { - self.setPaymentDetails(response.nonce); - self.placeOrder(); - } - }, + $.getJSON(self.clientTokenUrl).done(function (response) { + self.clientToken = response.clientToken; + self._initBraintree(); + }).fail(function (response) { + var failed = JSON.parse(response.responseText); - /** - * Error callback - * @param {Object} response - */ - onError: function (response) { - self.error(response.message); - } + $('body').trigger('processStop'); + self.error(failed.message); }); } catch (e) { $('body').trigger('processStop'); @@ -160,6 +140,54 @@ define([ } }, + /** + * Setup Braintree SDK + */ + _initBraintree: function () { + var self = this; + + this.disableEventListeners(); + + if (self.checkout) { + self.checkout.teardown(function () { + self.checkout = null; + }); + } + + self.braintree.setup(self.clientToken, 'custom', { + id: self.selector, + hostedFields: self.getHostedFields(), + + /** + * Triggered when sdk was loaded + */ + onReady: function (checkout) { + self.checkout = checkout; + $('body').trigger('processStop'); + self.enableEventListeners(); + }, + + /** + * Callback for success response + * @param {Object} response + */ + onPaymentMethodReceived: function (response) { + if (self.validateCardType()) { + self.setPaymentDetails(response.nonce); + self.placeOrder(); + } + }, + + /** + * Error callback + * @param {Object} response + */ + onError: function (response) { + self.error(response.message); + } + }); + }, + /** * Get hosted fields configuration * @returns {Object} @@ -211,6 +239,7 @@ define([ } if (event.type !== 'fieldStateChange') { + return false; } diff --git a/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml index ab294f8e797b7..c4152e1c3ebf9 100644 --- a/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Braintree/view/frontend/layout/checkout_index_index.xml @@ -35,6 +35,9 @@ false + + true + diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml index 1d60d19458a28..c1ef461ecae7c 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml @@ -29,6 +29,7 @@ $config = [ class="action-braintree-paypal-logo" disabled> Pay with PayPal + alt="escapeHtml(__('Pay with PayPal')) ?>" + title="escapeHtml(__('Pay with PayPal')) ?>"/> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js index 2834c0a683979..39bdf582c8cd7 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js @@ -79,6 +79,7 @@ define( */ onError: function (response) { braintree.showError($t('Payment ' + this.getTitle() + ' can\'t be initialized')); + this.isPlaceOrderActionAllowed(true); throw response.message; }, diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js index d2faac6ed792f..05c09abdb7b2e 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js @@ -61,7 +61,7 @@ define([ }, /** - * @returns {Bool} + * @returns {Boolean} */ isVaultEnabled: function () { return this.vaultEnabler.isVaultEnabled(); @@ -142,11 +142,20 @@ define([ return true; }, + /** + * Returns state of place order button + * @returns {Boolean} + */ + isButtonActive: function () { + return this.isActive() && this.isPlaceOrderActionAllowed(); + }, + /** * Trigger order placing */ placeOrderClick: function () { if (this.validateCardType()) { + this.isPlaceOrderActionAllowed(false); $(this.getSelector('submit')).trigger('click'); } }, diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index ca9d3686958b4..abf434bc6da26 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -7,6 +7,7 @@ define([ 'jquery', 'underscore', + 'mage/utils/wrapper', 'Magento_Checkout/js/view/payment/default', 'Magento_Braintree/js/view/payment/adapter', 'Magento_Checkout/js/model/quote', @@ -18,6 +19,7 @@ define([ ], function ( $, _, + wrapper, Component, Braintree, quote, @@ -204,7 +206,9 @@ define([ beforePlaceOrder: function (data) { this.setPaymentMethodNonce(data.nonce); - if (quote.billingAddress() === null && typeof data.details.billingAddress !== 'undefined') { + if ((this.isRequiredBillingAddress() || quote.billingAddress() === null) && + typeof data.details.billingAddress !== 'undefined' + ) { this.setBillingAddress(data.details, data.details.billingAddress); } @@ -218,8 +222,9 @@ define([ /** * Re-init PayPal Auth Flow + * @param {Function} callback - Optional callback */ - reInitPayPal: function () { + reInitPayPal: function (callback) { if (Braintree.checkout) { Braintree.checkout.teardown(function () { Braintree.checkout = null; @@ -228,6 +233,18 @@ define([ this.disableButton(); this.clientConfig.paypal.amount = this.grandTotalAmount; + this.clientConfig.paypal.shippingAddressOverride = this.getShippingAddress(); + + if (callback) { + this.clientConfig.onReady = wrapper.wrap( + this.clientConfig.onReady, + function (original, checkout) { + this.clientConfig.onReady = original; + original(checkout); + callback(); + }.bind(this) + ); + } Braintree.setConfig(this.clientConfig); Braintree.setup(); @@ -249,6 +266,14 @@ define([ return window.checkoutConfig.payment[this.getCode()].isAllowShippingAddressOverride; }, + /** + * Is billing address required from PayPal side + * @returns {Boolean} + */ + isRequiredBillingAddress: function () { + return window.checkoutConfig.payment[this.getCode()].isRequiredBillingAddress; + }, + /** * Get configuration for PayPal * @returns {Object} @@ -403,7 +428,11 @@ define([ * Triggers when customer click "Continue to PayPal" button */ payWithPayPal: function () { - if (additionalValidators.validate()) { + this.reInitPayPal(function () { + if (!additionalValidators.validate()) { + return; + } + try { Braintree.checkout.paypal.initAuthFlow(); } catch (e) { @@ -411,7 +440,7 @@ define([ message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.') }); } - } + }.bind(this)); }, /** diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html index d30186c8fb309..819b06ca75788 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html @@ -141,8 +141,7 @@ data-bind=" click: placeOrderClick, attr: {title: $t('Place Order')}, - css: {disabled: !isPlaceOrderActionAllowed()}, - enable: isActive() + enable: isButtonActive() " disabled> diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html index f5c8c15c8f3ba..e1f6a1b4c25ce 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html @@ -12,7 +12,7 @@ data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()" />