diff --git a/.github/.htaccess b/.github/.htaccess new file mode 100644 index 0000000000000..707c26b075e16 --- /dev/null +++ b/.github/.htaccess @@ -0,0 +1,8 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..4e82725a7fb08 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at engcom@magento.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000000000..dae954a0970b7 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# 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. 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 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 within two weeks, the pull request will be closed. + + +## Contribution requirements + +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 +* 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). 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 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 + +Please note that this project is released with a Contributor Code of Conduct. We expect you to agree to its terms when participating in this project. +The full text is available in the repository [Wiki](https://github.com/magento/magento2/wiki/Magento-Code-of-Conduct). diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000..2b1720ccaabae --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,36 @@ + + +### Preconditions (*) + +1. +2. + +### Steps to reproduce (*) + +1. +2. +3. + +### Expected result (*) + +1. [Screenshots, logs or description] + +### Actual result (*) + +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 new file mode 100644 index 0000000000000..f191bd9aaba67 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ + + + + +### Description (*) + + +### Fixed Issues (if relevant) + +1. magento/magento2#: Issue title +2. ... + +### Manual testing scenarios (*) + +1. ... +2. ... + +### 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) + - [ ] All automated tests passed successfully (all builds on Travis CI are green) diff --git a/.gitignore b/.gitignore index bae558e0e5b9a..75e5f11d8a8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /.metadata /.project /.settings +/.vscode atlassian* /nbproject /robots.txt @@ -32,7 +33,6 @@ atlassian* /.php_cs /.php_cs.cache /grunt-config.json -/dev/tools/grunt/configs/local-themes.js /pub/media/*.* !/pub/media/.htaccess diff --git a/.htaccess b/.htaccess index 90b9c16a5a8c0..4298b10d9ca7a 100644 --- a/.htaccess +++ b/.htaccess @@ -36,7 +36,7 @@ ############################################ ## adjust memory limit - php_value memory_limit 768M + php_value memory_limit 756M php_value max_execution_time 18000 ############################################ @@ -59,7 +59,7 @@ ############################################ ## adjust memory limit - php_value memory_limit 768M + php_value memory_limit 756M php_value max_execution_time 18000 ############################################ @@ -203,76 +203,166 @@ RedirectMatch 403 /\.git - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all - - - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + + + + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + 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 diff --git a/.htaccess.sample b/.htaccess.sample index 3b61bb672ec8a..a521a347232f5 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -35,7 +35,7 @@ ############################################ ## adjust memory limit - php_value memory_limit 768M + php_value memory_limit 756M php_value max_execution_time 18000 ############################################ @@ -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 ############################################ @@ -179,76 +180,166 @@ RedirectMatch 403 /\.git - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all - - - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + order allow,deny + deny from all + + = 2.4> + Require all denied + + + + + order allow,deny + deny from all + + = 2.4> + Require all denied + - order allow,deny - deny from all + + 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 diff --git a/.php_cs.dist b/.php_cs.dist index 0f254c63283bd..87483d5b33a15 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -4,10 +4,6 @@ * See COPYING.txt for license details. */ -/** - * Pre-commit hook installation: - * vendor/bin/static-review.php hook:install dev/tools/Magento/Tools/StaticReview/pre-commit .git/hooks/pre-commit - */ $finder = PhpCsFixer\Finder::create() ->name('*.phtml') ->exclude('dev/tests/functional/generated') diff --git a/.travis.yml b/.travis.yml index 42c2bcab0c901..6e6f3359767b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,11 +15,13 @@ language: php php: - 7.0 - 7.1 +git: + depth: 5 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 @@ -29,8 +31,7 @@ env: - TEST_SUITE=integration INTEGRATION_INDEX=1 - TEST_SUITE=integration INTEGRATION_INDEX=2 - TEST_SUITE=integration INTEGRATION_INDEX=3 - - TEST_SUITE=functional ACCEPTANCE_INDEX=1 - - TEST_SUITE=functional ACCEPTANCE_INDEX=2 + - TEST_SUITE=functional matrix: exclude: - php: 7.0 @@ -40,9 +41,7 @@ matrix: - php: 7.0 env: TEST_SUITE=js GRUNT_COMMAND=static - php: 7.0 - env: TEST_SUITE=functional ACCEPTANCE_INDEX=1 - - php: 7.0 - env: TEST_SUITE=functional ACCEPTANCE_INDEX=2 + env: TEST_SUITE=functional cache: apt: true directories: @@ -55,7 +54,6 @@ install: composer install --no-interaction --prefer-dist before_script: ./dev/travis/before_script.sh script: # Set arguments for variants of phpunit based tests; '|| true' prevents failing script when leading test fails - - test $TEST_SUITE = "static" && TEST_FILTER='--filter "Magento\\Test\\Php\\LiveCodeTest"' || true - test $TEST_SUITE = "functional" && TEST_FILTER='dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests.php' || true # The scripts for grunt/phpunit type tests diff --git a/.user.ini b/.user.ini index 8c0b765e0551c..bfc3a86d88e20 100644 --- a/.user.ini +++ b/.user.ini @@ -1,4 +1,4 @@ -memory_limit = 768M +memory_limit = 756M max_execution_time = 18000 session.auto_start = off suhosin.session.cryptua = off \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ef841ec0337f2..95772083f3a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2016 @@ +2.2.6 +============= +* GitHub issues: + * [#13296](https://github.com/magento/magento2/issues/13296) -- Category name with special characters brakes in url rewrites category tree (fixed in [magento/magento2#13397](https://github.com/magento/magento2/pull/13397)) + * [#4788](https://github.com/magento/magento2/issues/4788) -- Wrong sitemap product url (fixed in [magento/magento2#14338](https://github.com/magento/magento2/pull/14338)) + * [#14669](https://github.com/magento/magento2/issues/14669) -- Css class "empty" is always present on minicart dropdown (fixed in [magento/magento2#14715](https://github.com/magento/magento2/pull/14715)) + * [#4389](https://github.com/magento/magento2/issues/4389) -- Widget cache error (fixed in [magento/magento2#12764](https://github.com/magento/magento2/pull/12764)) + * [#13765](https://github.com/magento/magento2/issues/13765) -- "cart" section data gets loaded 3 times on cart page (2.2.2) (fixed in [magento/magento2#14314](https://github.com/magento/magento2/pull/14314)) + * [#1821](https://github.com/magento/magento2/issues/1821) -- CSS load order incorrect using default_head_blocks.xml (fixed in [magento/magento2#14290](https://github.com/magento/magento2/pull/14290)) + * [#14692](https://github.com/magento/magento2/issues/14692) -- 'validate-grouped-qty' validation is meaningless (fixed in [magento/magento2#14752](https://github.com/magento/magento2/pull/14752)) + * [#11396](https://github.com/magento/magento2/issues/11396) -- app:config:dump locks every configuration setting no alternative to dump specific setting only (fixed in [magento/magento2#12410](https://github.com/magento/magento2/pull/12410)) + * [#9580](https://github.com/magento/magento2/issues/9580) -- Quote Attribute trigger_recollect causes a timeout (fixed in [magento/magento2#14719](https://github.com/magento/magento2/pull/14719)) + * [#13944](https://github.com/magento/magento2/issues/13944) -- Stores -> Terms and Conditions - No Store View shown (fixed in [magento/magento2#14546](https://github.com/magento/magento2/pull/14546)) + * [#5726](https://github.com/magento/magento2/issues/5726) -- Reset Password Email Issue on Multi Store from Admin (fixed in [magento/magento2#14800](https://github.com/magento/magento2/pull/14800)) + * [#14274](https://github.com/magento/magento2/issues/14274) -- Quick search fires error (fixed in [magento/magento2#14839](https://github.com/magento/magento2/pull/14839)) + * [#7861](https://github.com/magento/magento2/issues/7861) -- Using search in Admin panel, and choosing "% in Products" returns full catalog (fixed in [magento/magento2#12735](https://github.com/magento/magento2/pull/12735)) + * [#12193](https://github.com/magento/magento2/issues/12193) -- Catalog not filtered by admin search bar (fixed in [magento/magento2#12735](https://github.com/magento/magento2/pull/12735)) + * [#5768](https://github.com/magento/magento2/issues/5768) -- Magento 2.0.7 XML sitemap is not generated by schedule (fixed in [magento/magento2#14822](https://github.com/magento/magento2/pull/14822)) + * [#14855](https://github.com/magento/magento2/issues/14855) -- Adding an * to do a customer search. (fixed in [magento/magento2#14905](https://github.com/magento/magento2/pull/14905)) + * [#14869](https://github.com/magento/magento2/issues/14869) -- M 2.2.3 price per website - wrong price at backend by a create order after update (fixed in [magento/magento2#14904](https://github.com/magento/magento2/pull/14904)) + * [#10395](https://github.com/magento/magento2/issues/10395) -- REMOTE_IP gets saved partially when using IPV6 (fixed in [magento/magento2#14976](https://github.com/magento/magento2/pull/14976)) + * [#12285](https://github.com/magento/magento2/issues/12285) -- The option false for mobile device don't work in product view page gallery (fixed in [magento/magento2#15020](https://github.com/magento/magento2/pull/15020)) + * [#15009](https://github.com/magento/magento2/issues/15009) -- [2.2.4] Gallery theme variables being ignored (fixed in [magento/magento2#15020](https://github.com/magento/magento2/pull/15020)) + * [#13460](https://github.com/magento/magento2/issues/13460) -- Allmethods config source model does not always report the full list of payment methods (fixed in [magento/magento2#15032](https://github.com/magento/magento2/pull/15032)) + * [#4301](https://github.com/magento/magento2/issues/4301) -- Hit fast twice F5 on checout page, customer loggs out automatically (fixed in [magento/magento2#14428](https://github.com/magento/magento2/pull/14428)) + * [#12362](https://github.com/magento/magento2/issues/12362) -- Concurrent (quick reload) requests on checkout cause cart to empty - related to session_regenerate_id (fixed in [magento/magento2#14428](https://github.com/magento/magento2/pull/14428)) + * [#13427](https://github.com/magento/magento2/issues/13427) -- [2.1.11] Add to cart, try to checkout, cart is empty but mini-cart has items. (fixed in [magento/magento2#14428](https://github.com/magento/magento2/pull/14428)) + * [#11354](https://github.com/magento/magento2/issues/11354) -- Merged CSS file name generation (fixed in [magento/magento2#15144](https://github.com/magento/magento2/pull/15144)) + * [#14104](https://github.com/magento/magento2/issues/14104) -- Admin Section is not visible in backend on production mode (fixed in [magento/magento2#15174](https://github.com/magento/magento2/pull/15174)) + * [#7399](https://github.com/magento/magento2/issues/7399) -- Modal UI: clickableOverlay option doesn't work (fixed in [magento/magento2#15172](https://github.com/magento/magento2/pull/15172)) + * [#14987](https://github.com/magento/magento2/issues/14987) -- Invisible breadcrumbs at product page when mageMenu widget is not used (fixed in [magento/magento2#15178](https://github.com/magento/magento2/pull/15178)) + * [#13530](https://github.com/magento/magento2/issues/13530) -- "Template file 'header.html' is not found" error while trying to save Design Configuration. (fixed in [magento/magento2#15137](https://github.com/magento/magento2/pull/15137)) + * [#14968](https://github.com/magento/magento2/issues/14968) -- Can't change the applied theme in 2.2.4 (fixed in [magento/magento2#15137](https://github.com/magento/magento2/pull/15137)) + * [#15121](https://github.com/magento/magento2/issues/15121) -- Magento 2.2.4 - Condition Category Chooser Crashes Page if Store has Several Nested Categories (fixed in [magento/magento2#15265](https://github.com/magento/magento2/pull/15265)) + * [#15334](https://github.com/magento/magento2/issues/15334) -- Purchased Order Form button should visible properly (fixed in [magento/magento2#15331](https://github.com/magento/magento2/pull/15331) and [magento/magento2#15372](https://github.com/magento/magento2/pull/15372)) + * [#15352](https://github.com/magento/magento2/issues/15352) -- Reformat the javascript code as per magento standards. (fixed in [magento/magento2#15343](https://github.com/magento/magento2/pull/15343)) + * [#15355](https://github.com/magento/magento2/issues/15355) -- Function is unnecessarily called multiple time (fixed in [magento/magento2#15346](https://github.com/magento/magento2/pull/15346)) + * [#15319](https://github.com/magento/magento2/issues/15319) -- misleading data-container in product list (fixed in [magento/magento2#15350](https://github.com/magento/magento2/pull/15350)) + * [#15354](https://github.com/magento/magento2/issues/15354) -- Refactor javascript code of button split widget call js component (fixed in [magento/magento2#15351](https://github.com/magento/magento2/pull/15351)) + * [#14941](https://github.com/magento/magento2/issues/14941) -- Unnecessary recalculation of product list pricing causes huge slowdowns (fixed in [magento/magento2#15089](https://github.com/magento/magento2/pull/15089)) + * [#14747](https://github.com/magento/magento2/issues/14747) -- Newsletter subscription confirmation message does not display after clicking link in email (fixed in [magento/magento2#15247](https://github.com/magento/magento2/pull/15247)) + * [#15037](https://github.com/magento/magento2/issues/15037) -- Product Details Page breadcrumbs cause syntax error on products containing quotes (fixed in [magento/magento2#15347](https://github.com/magento/magento2/pull/15347)) + * [#15118](https://github.com/magento/magento2/issues/15118) -- Responsive Design, Footers do not snap to bottom of screen on mobile devices (fixed in [magento/magento2#15353](https://github.com/magento/magento2/pull/15353) and [magento/magento2#17006](https://github.com/magento/magento2/pull/17006)) + * [#15192](https://github.com/magento/magento2/issues/15192) -- Module Manager module grid is not working Magento 2.2.4 (fixed in [magento/magento2#15211](https://github.com/magento/magento2/pull/15211)) + * [#13793](https://github.com/magento/magento2/issues/13793) -- Submitting search form (mini) with enter key fires event handlers bound by jquery twice (fixed in [magento/magento2#15340](https://github.com/magento/magento2/pull/15340)) + * [#15361](https://github.com/magento/magento2/issues/15361) -- Comments are not translated for Signifyd module. (fixed in [magento/magento2#15364](https://github.com/magento/magento2/pull/15364)) + * [#15356](https://github.com/magento/magento2/issues/15356) -- Refactore javascript for module URL rewrite (fixed in [magento/magento2#15422](https://github.com/magento/magento2/pull/15422)) + * [#10210](https://github.com/magento/magento2/issues/10210) -- Transport variable can not be altered in email_invoice_set_template_vars_before Event (fixed in [magento/magento2#15040](https://github.com/magento/magento2/pull/15040) and [magento/magento2#16599](https://github.com/magento/magento2/pull/16599)) + * [#4977](https://github.com/magento/magento2/issues/4977) -- Magnifier doesn't work with mode set to inner (fixed in [magento/magento2#15382](https://github.com/magento/magento2/pull/15382)) + * [#15469](https://github.com/magento/magento2/issues/15469) -- lib/web/mage/dropdowns.js fails when autoclose is set to true (fixed in [magento/magento2#15499](https://github.com/magento/magento2/pull/15499)) + * [#14153](https://github.com/magento/magento2/issues/14153) -- UI Component listing action column outside of screen when too many columns (fixed in [magento/magento2#15459](https://github.com/magento/magento2/pull/15459)) + * [#15467](https://github.com/magento/magento2/issues/15467) -- Cart does not load when Configuration product option is deleted and that option is in the cart (fixed in [magento/magento2#15468](https://github.com/magento/magento2/pull/15468)) + * [#15564](https://github.com/magento/magento2/issues/15564) -- 2.2.4 Created admin token has no access (fixed in [magento/magento2#15598](https://github.com/magento/magento2/pull/15598)) + * [#10346](https://github.com/magento/magento2/issues/10346) -- Deadlock occurs using REST API & OAuth 1.0a under high concurrency (fixed in [magento/magento2#13328](https://github.com/magento/magento2/pull/13328)) + * [#15348](https://github.com/magento/magento2/issues/15348) -- Multiple Payment Methods Enabled is giving error in console "Found 3 Elements with non - unique Id" (fixed in [magento/magento2#15349](https://github.com/magento/magento2/pull/15349)) + * [#13415](https://github.com/magento/magento2/issues/13415) -- Duplicated elements id in checkout page (fixed in [magento/magento2#15585](https://github.com/magento/magento2/pull/15585)) + * [#15590](https://github.com/magento/magento2/issues/15590) -- Typo in tests / setCateroryIds([]) (fixed in [magento/magento2#15621](https://github.com/magento/magento2/pull/15621)) + * [#7897](https://github.com/magento/magento2/issues/7897) -- Menu widget submenu alignment (fixed in [magento/magento2#15645](https://github.com/magento/magento2/pull/15645)) + * [#15565](https://github.com/magento/magento2/issues/15565) -- Getting wrong frontend-controller, when using storecodes in urls (fixed in [magento/magento2#15566](https://github.com/magento/magento2/pull/15566)) + * [#6058](https://github.com/magento/magento2/issues/6058) -- IE11 user login email validation fails if field has leading or trailing space (fixed in [magento/magento2#15365](https://github.com/magento/magento2/pull/15365) and [magento/magento2#16192](https://github.com/magento/magento2/pull/16192) and [magento/magento2#16564](https://github.com/magento/magento2/pull/16564) and [magento/magento2#16595](https://github.com/magento/magento2/pull/16595)) + * [#15210](https://github.com/magento/magento2/issues/15210) -- Advanced pricing pagination issue (fixed in [magento/magento2#15614](https://github.com/magento/magento2/pull/15614)) + * [#12221](https://github.com/magento/magento2/issues/12221) -- Google analytics pageview being triggered twice (fixed in [magento/magento2#15765](https://github.com/magento/magento2/pull/15765)) + * [#15510](https://github.com/magento/magento2/issues/15510) -- First PDF download / export after login (fixed in [magento/magento2#15539](https://github.com/magento/magento2/pull/15539)) + * [#15608](https://github.com/magento/magento2/issues/15608) -- Styling select by changing less variables in Luma theme doesn't work as expected (fixed in [magento/magento2#15734](https://github.com/magento/magento2/pull/15734)) + * [#14966](https://github.com/magento/magento2/issues/14966) -- Disabling product does not remove it from the flat index (fixed in [magento/magento2#15019](https://github.com/magento/magento2/pull/15019)) + * [#11477](https://github.com/magento/magento2/issues/11477) -- Magento REST API Schema (Swagger) is not compatible with Search Criteria (fixed in [magento/magento2#15322](https://github.com/magento/magento2/pull/15322)) + * [#14056](https://github.com/magento/magento2/issues/14056) -- Coupon API not working for guest user (fixed in [magento/magento2#15320](https://github.com/magento/magento2/pull/15320)) + * [#15660](https://github.com/magento/magento2/issues/15660) -- Wrong order amount on dashboard on Last orders listing when having more than one website with different currencies (fixed in [magento/magento2#15661](https://github.com/magento/magento2/pull/15661)) + * [#15588](https://github.com/magento/magento2/issues/15588) -- Images in XML sitemap are always linked to base store in multistore (fixed in [magento/magento2#15689](https://github.com/magento/magento2/pull/15689)) + * [#15822](https://github.com/magento/magento2/issues/15822) -- SQL Error: ambiguous column 'customer_group_id' in 'All customers' page in admin when extension attribute table is joined (fixed in [magento/magento2#15826](https://github.com/magento/magento2/pull/15826)) + * [#15323](https://github.com/magento/magento2/issues/15323) -- limiter float too generic (fixed in [magento/magento2#15878](https://github.com/magento/magento2/pull/15878)) + * [#14999](https://github.com/magento/magento2/issues/14999) -- Changing @tab-content__border variable has no effect in Blank theme (fixed in [magento/magento2#15914](https://github.com/magento/magento2/pull/15914)) + * [#15308](https://github.com/magento/magento2/issues/15308) -- extraneous margins on product list and product list items (fixed in [magento/magento2#15936](https://github.com/magento/magento2/pull/15936)) + * [#16047](https://github.com/magento/magento2/issues/16047) -- inline-block issue in name form (fixed in [magento/magento2#16048](https://github.com/magento/magento2/pull/16048)) + * [#15213](https://github.com/magento/magento2/issues/15213) -- Alignment & overlapping Issue on every Home page & category page of Hot Seller section (fixed in [magento/magento2#15893](https://github.com/magento/magento2/pull/15893)) + * [#15832](https://github.com/magento/magento2/issues/15832) -- No button-primary__font-weight (fixed in [magento/magento2#16012](https://github.com/magento/magento2/pull/16012)) + * [#13692](https://github.com/magento/magento2/issues/13692) -- In payment step of checkout I cannot unselect #billing-save-in-address-book checkbox in non-first payment method (fixed in [magento/magento2#15344](https://github.com/magento/magento2/pull/15344)) + * [#15255](https://github.com/magento/magento2/issues/15255) -- Customer who exceeded max login failures not able to login even after reset password (fixed in [magento/magento2#15534](https://github.com/magento/magento2/pull/15534)) + * [#15220](https://github.com/magento/magento2/issues/15220) -- 2.2.4: navigation dropdown caret icon missing (jQuery UI) (fixed in [magento/magento2#16082](https://github.com/magento/magento2/pull/16082)) + * [#16079](https://github.com/magento/magento2/issues/16079) -- Need information about translating issue (Magento Swatches Js) (fixed in [magento/magento2#16190](https://github.com/magento/magento2/pull/16190)) + * [#16184](https://github.com/magento/magento2/issues/16184) -- Argument 1 passed to Magento\Sales\Model\Order\Payment must be an instance of Magento\Framework\DataObject, none given (fixed in [magento/magento2#16194](https://github.com/magento/magento2/pull/16194)) + * [#8222](https://github.com/magento/magento2/issues/8222) -- Estimate Shipping and Tax Form not works due to js error in collapsible.js [proposed fix] (fixed in [magento/magento2#16213](https://github.com/magento/magento2/pull/16213)) + * [#15501](https://github.com/magento/magento2/issues/15501) -- M2.2.4 missing meta title tag and doesn't show product name if meta title is empty (fixed in [magento/magento2#15532](https://github.com/magento/magento2/pull/15532)) + * [#15627](https://github.com/magento/magento2/issues/15627) -- Product order in category changed after update to Magento 2.2.4 (fixed in [magento/magento2#15629](https://github.com/magento/magento2/pull/15629)) + * [#9307](https://github.com/magento/magento2/issues/9307) -- Color attribute taking swatch instead of Drop down option for configurable options, (fixed in [magento/magento2#12771](https://github.com/magento/magento2/pull/12771)) + * [#9923](https://github.com/magento/magento2/issues/9923) -- Upgrading to 2.1.7 changed dropdown attributes to swatches (fixed in [magento/magento2#12771](https://github.com/magento/magento2/pull/12771)) + * [#11403](https://github.com/magento/magento2/issues/11403) -- Product Attributes Not Updating on Frontend (fixed in [magento/magento2#12771](https://github.com/magento/magento2/pull/12771)) + * [#11703](https://github.com/magento/magento2/issues/11703) -- Changing Swatches to Drop-down does not remove swatches from existing products (fixed in [magento/magento2#12771](https://github.com/magento/magento2/pull/12771)) + * [#12695](https://github.com/magento/magento2/issues/12695) -- Unable to change attribute type from swatch to dropdown (fixed in [magento/magento2#12771](https://github.com/magento/magento2/pull/12771)) + * [#14895](https://github.com/magento/magento2/issues/14895) -- Change Password warning message appear two times (fixed in [magento/magento2#15774](https://github.com/magento/magento2/pull/15774)) + * [#15205](https://github.com/magento/magento2/issues/15205) -- Upgraded to Magento 2.2.4 from Magento 2.1.9 - Locale and Store Configuration issues 'Store View' Locale not being used on frontend, 'Default Config' Locale being used instead (fixed in [magento/magento2#15929](https://github.com/magento/magento2/pull/15929)) + * [#15245](https://github.com/magento/magento2/issues/15245) -- 2.2.4: Wrong home page loaded in multi store setup (fixed in [magento/magento2#15929](https://github.com/magento/magento2/pull/15929)) + * [#15345](https://github.com/magento/magento2/issues/15345) -- Template syntax in block file (fixed in [magento/magento2#15339](https://github.com/magento/magento2/pull/15339)) + * [#7379](https://github.com/magento/magento2/issues/7379) -- Calendar widget (jQuery UI DatePicker) with numberOfMonths = 2 or more (fixed in [magento/magento2#16279](https://github.com/magento/magento2/pull/16279)) + * [#16378](https://github.com/magento/magento2/issues/16378) -- Wrong placeholder for password field in the checkout page (fixed in [magento/magento2#16379](https://github.com/magento/magento2/pull/16379)) + * [#15218](https://github.com/magento/magento2/issues/15218) -- "Confirmation request" email is sent on customer's newsletter unsubscription (fixed in [magento/magento2#15464](https://github.com/magento/magento2/pull/15464)) + * [#14593](https://github.com/magento/magento2/issues/14593) -- Press Esc Key on modal generate a jquery UI error (fixed in [magento/magento2#16477](https://github.com/magento/magento2/pull/16477)) + * [#11717](https://github.com/magento/magento2/issues/11717) -- Wrong price amount on product page (fixed in [magento/magento2#15909](https://github.com/magento/magento2/pull/15909) and [magento/magento2#16590](https://github.com/magento/magento2/pull/16590)) + * [#16529](https://github.com/magento/magento2/issues/16529) -- Rewriting product listing widget block breaks its template rendering. (fixed in [magento/magento2#16530](https://github.com/magento/magento2/pull/16530)) + * [#15940](https://github.com/magento/magento2/issues/15940) -- Wrong end of month at Reports for Europe/Berlin time zone if month contains 31 day (fixed in [magento/magento2#16584](https://github.com/magento/magento2/pull/16584)) + * [#16174](https://github.com/magento/magento2/issues/16174) -- Admin tabs order not working properly (fixed in [magento/magento2#16175](https://github.com/magento/magento2/pull/16175)) + * [#16703](https://github.com/magento/magento2/issues/16703) -- User Agent Rules table headers do match content of rows. (fixed in [magento/magento2#16704](https://github.com/magento/magento2/pull/16704)) + * [#15848](https://github.com/magento/magento2/issues/15848) -- no navigation-level0-item__hover__color (fixed in [magento/magento2#16732](https://github.com/magento/magento2/pull/16732)) + * [#5067](https://github.com/magento/magento2/issues/5067) -- Custom option values do not save correctly (fixed in [magento/magento2#13569](https://github.com/magento/magento2/pull/13569)) + * [#14351](https://github.com/magento/magento2/issues/14351) -- Product import doesn't change `Enable Qty Increments` field (fixed in [magento/magento2#14379](https://github.com/magento/magento2/pull/14379)) + * [#16764](https://github.com/magento/magento2/issues/16764) -- Rating Star issue on Product detail Page. (fixed in [magento/magento2#16766](https://github.com/magento/magento2/pull/16766)) + * [#5316](https://github.com/magento/magento2/issues/5316) -- [2.1.0] HTML minification problem with php tag with a comment and no space at the end (fixed in [magento/magento2#16916](https://github.com/magento/magento2/pull/16916)) + * [#6264](https://github.com/magento/magento2/issues/6264) -- Error in Admin > products when module Reviews is disabled (fixed in [magento-partners/magento2ee#70](https://github.com/magento-partners/magento2ee/pull/70)) + * [#6504](https://github.com/magento/magento2/issues/6504) -- Magento 2.1 CE: Breadcrumbs on homepage and 404 in multistore (fixed in [magento-partners/magento2ee#72](https://github.com/magento-partners/magento2ee/pull/72)) + * [#16843](https://github.com/magento/magento2/issues/16843) -- Magento 2.2.5: Configurable Product with Only Size Options (No Color Options) Shows No Image in Cart (fixed in [magento/magento2#16863](https://github.com/magento/magento2/pull/16863)) + * [#8131](https://github.com/magento/magento2/issues/8131) -- Magento 2.1.3 - There is a bug in advanced search form regarding validation messages (fixed in [magento/magento2#16952](https://github.com/magento/magento2/pull/16952)) + * [#14476](https://github.com/magento/magento2/issues/14476) -- Mobile device style groups incorrect order in _responsive.less (fixed in [magento/magento2#16959](https://github.com/magento/magento2/pull/16959)) + * [#15393](https://github.com/magento/magento2/issues/15393) -- "Multiple Select" product attributes do not render HTML tags on the storefront view (fixed in [magento/magento2#15687](https://github.com/magento/magento2/pull/15687)) + * [#3535](https://github.com/magento/magento2/issues/3535) -- Print pdf don't delete file in var folder (fixed in [magento/magento2#16401](https://github.com/magento/magento2/pull/16401)) + * [#14517](https://github.com/magento/magento2/issues/14517) -- PDF invoices in /var folder (fixed in [magento/magento2#16401](https://github.com/magento/magento2/pull/16401)) + * [#16273](https://github.com/magento/magento2/issues/16273) -- Method $product->getUrlInStore() returning extremely long URLs, could be a bug (fixed in [magento/magento2#16468](https://github.com/magento/magento2/pull/16468)) +* GitHub pull requests: + * [magento/magento2#13397](https://github.com/magento/magento2/pull/13397) -- magento/magento2#13296: Category name with special characters brakes … (by @vinayshah) + * [magento/magento2#14338](https://github.com/magento/magento2/pull/14338) -- Fixing wrong sitemap product url #4788 (by @DenisSaltanahmedov) + * [magento/magento2#14715](https://github.com/magento/magento2/pull/14715) -- #14669 Css class "empty" is always present on minicart dropdown (by @Karlasa) + * [magento/magento2#14716](https://github.com/magento/magento2/pull/14716) -- [2.2] Fix - minicart label fixed size issue (by @Karlasa) + * [magento/magento2#12764](https://github.com/magento/magento2/pull/12764) -- magento/magento2#4389 Widget cache error (by @AlexandrKozyr) + * [magento/magento2#14314](https://github.com/magento/magento2/pull/14314) -- magento/magento2#13765 Excess requests 'customer data' on checkout cart page were fixed (by @andrewbess) + * [magento/magento2#14538](https://github.com/magento/magento2/pull/14538) -- [Forwardport] Fix HTML tags in meta description (by @davidwindell) + * [magento/magento2#14742](https://github.com/magento/magento2/pull/14742) -- Allow multiple tabs ui_components on a page (by @FreekVandeursen) + * [magento/magento2#14751](https://github.com/magento/magento2/pull/14751) -- Deleting CMS page via webapi/cron should remove related URL rewrites (by @unicoder88) + * [magento/magento2#14290](https://github.com/magento/magento2/pull/14290) -- CSS load order incorrect using default_head_blocks.xml #1821 (by @SergeyDmitruk) + * [magento/magento2#14707](https://github.com/magento/magento2/pull/14707) -- Catalog price rule save is too slow #13378 (by @chrom) + * [magento/magento2#14752](https://github.com/magento/magento2/pull/14752) -- Fix to allow use decimals less then 1 in subproducts qty. (by @likemusic) + * [magento/magento2#14769](https://github.com/magento/magento2/pull/14769) -- [Backport] Add expanded documentation to AdapterInterface::update (by @navarr) + * [magento/magento2#14790](https://github.com/magento/magento2/pull/14790) -- Update Readme file for magento2 repository (by @sidolov) + * [magento/magento2#12410](https://github.com/magento/magento2/pull/12410) -- Add argument on app:config:dump to skip dumping all system settings. (by @jalogut) + * [magento/magento2#14719](https://github.com/magento/magento2/pull/14719) -- Fixed setting of triggerRecollection flag (by @philippsander) + * [magento/magento2#14753](https://github.com/magento/magento2/pull/14753) -- [Backport] Fix bug with retry connect and custom db port (by @julienanquetil) + * [magento/magento2#14765](https://github.com/magento/magento2/pull/14765) -- Display Wrong Data On Cart Update Page (by @nit-it) + * [magento/magento2#14546](https://github.com/magento/magento2/pull/14546) -- Fix issue #13944. Show Store Views in Terms and Conditions grid. (by @afirlejczyk) + * [magento/magento2#14726](https://github.com/magento/magento2/pull/14726) -- Fix/navigation order function (by @luke-denton-aligent) + * [magento/magento2#14800](https://github.com/magento/magento2/pull/14800) -- #5726 - Fix reset password link with appropriate customer store (by @rodrigowebjump) + * [magento/magento2#14844](https://github.com/magento/magento2/pull/14844) -- Updated readme.md file 2.2-develop (by @sidolov) + * [magento/magento2#14795](https://github.com/magento/magento2/pull/14795) -- Invoice grid shows wrong shipping & handling for partial items invoice. It shows order's shipping & handling instead if invoiced shipping& handling charge (by @ankurvr) + * [magento/magento2#14833](https://github.com/magento/magento2/pull/14833) -- Fix a non well formed numeric value encountered on Magento/Directory/… (by @bmxmale) + * [magento/magento2#14627](https://github.com/magento/magento2/pull/14627) -- Fix: Datepicker problem when using non en-US locale. (by @tao-s) + * [magento/magento2#14836](https://github.com/magento/magento2/pull/14836) -- Use index sitemap name as prefix in split sitemaps (by @jameshalsall) + * [magento/magento2#14839](https://github.com/magento/magento2/pull/14839) -- Back port pull 14301 (by @julienanquetil) + * [magento/magento2#12566](https://github.com/magento/magento2/pull/12566) -- Move isAllowed method from AccessChangeQuoteControl to separate service (by @JeroenVanLeusden) + * [magento/magento2#14699](https://github.com/magento/magento2/pull/14699) -- [2.2] Optimize ID to SKU lookup of tier prices (by @toddbc) + * [magento/magento2#14829](https://github.com/magento/magento2/pull/14829) -- Add statement to 'beforeSave' method to allow app:config:import (by @bmxmale) + * [magento/magento2#12735](https://github.com/magento/magento2/pull/12735) -- magento/magento2#12193 Catalog not filtered by admin search bar (by @hannassy) + * [magento/magento2#14822](https://github.com/magento/magento2/pull/14822) -- Add default schedule config for sitemap_generate job (by @jameshalsall) + * [magento/magento2#14876](https://github.com/magento/magento2/pull/14876) -- Changed return type of addToCartPostParams to array (by @LordZardeck) + * [magento/magento2#14892](https://github.com/magento/magento2/pull/14892) -- Corrected @param in comment block (by @Yogeshks) + * [magento/magento2#14609](https://github.com/magento/magento2/pull/14609) -- Code cleanup, add more visibility (by @thomas-blackbird) + * [magento/magento2#14891](https://github.com/magento/magento2/pull/14891) -- Fix typo in doc for updateSpecificCoupons (by @sjb9774) + * [magento/magento2#14893](https://github.com/magento/magento2/pull/14893) -- [Backport] Fix aggregations use statements and return values (by @rogyar) + * [magento/magento2#14896](https://github.com/magento/magento2/pull/14896) -- Removed extra spaces from language file (by @Yogeshks) + * [magento/magento2#14905](https://github.com/magento/magento2/pull/14905) -- FIX for issue#14855 - Adding an * to do a customer search (by @phoenix128) + * [magento/magento2#14820](https://github.com/magento/magento2/pull/14820) -- Duplicate Order Confirmation Emails for PayPal Express checkout order (by @rocketweb) + * [magento/magento2#14904](https://github.com/magento/magento2/pull/14904) -- FIX for issue#14869 - Wrong price at backend after update (by @phoenix128) + * [magento/magento2#14928](https://github.com/magento/magento2/pull/14928) -- Removed extra close tag (by @Yogeshks) + * [magento/magento2#14886](https://github.com/magento/magento2/pull/14886) -- Fixed issue products grid operations in admin cart price rule edit page for user which has no access to CatalogRule module (by @Neos2007) + * [magento/magento2#14874](https://github.com/magento/magento2/pull/14874) -- Fix infinite checkout loader when some script wasn't loaded correctly because of network error (by @vovayatsyuk) + * [magento/magento2#14923](https://github.com/magento/magento2/pull/14923) -- Move customer.account.dashboard.info.extra block to contact information (by @JeroenVanLeusden) + * [magento/magento2#14939](https://github.com/magento/magento2/pull/14939) -- Renamed "Add Block Names to Hints" config setting to represent what it actually does (by @chris-pook) + * [magento/magento2#14935](https://github.com/magento/magento2/pull/14935) -- Change 'Update'-button visibility on change qty event. (by @likemusic) + * [magento/magento2#14963](https://github.com/magento/magento2/pull/14963) -- Fixed Overlay Problems (by @ArtiDjeims) + * [magento/magento2#14946](https://github.com/magento/magento2/pull/14946) -- use "Module_Name::template/path" format instead of using template/path i… (by @Jakhotiya) + * [magento/magento2#14976](https://github.com/magento/magento2/pull/14976) -- Changed the length of the remote_ip field to store ipv6 addresses (by @georgeschiopu) + * [magento/magento2#15015](https://github.com/magento/magento2/pull/15015) -- Fix typo in less button definition (by @shochdoerfer) + * [magento/magento2#15018](https://github.com/magento/magento2/pull/15018) -- chore: upgrade Node.js to 8 (by @DanielRuf) + * [magento/magento2#15023](https://github.com/magento/magento2/pull/15023) -- Fixed typos in .less files (by @kalpmehta) + * [magento/magento2#15002](https://github.com/magento/magento2/pull/15002) -- small optimization in if-condition (by @likemusic) + * [magento/magento2#15012](https://github.com/magento/magento2/pull/15012) -- fix: set message-success in setup if we already have the latest version (by @DanielRuf) + * [magento/magento2#15017](https://github.com/magento/magento2/pull/15017) -- chore: use random_int() in some places (by @DanielRuf) + * [magento/magento2#15016](https://github.com/magento/magento2/pull/15016) -- chore: checkout last 5 commits (by @DanielRuf) + * [magento/magento2#15020](https://github.com/magento/magento2/pull/15020) -- [2.2-develop] Update Gallery Template to handle boolean config Variables (by @gwharton) + * [magento/magento2#15032](https://github.com/magento/magento2/pull/15032) -- [TASK] Fix overriding of payment methods in getPaymentMethodList (by @mash1t) + * [magento/magento2#15053](https://github.com/magento/magento2/pull/15053) -- Fixes typo (by @jee1mr) + * [magento/magento2#14967](https://github.com/magento/magento2/pull/14967) -- Format the javascript code (by @Yogeshks) + * [magento/magento2#15067](https://github.com/magento/magento2/pull/15067) -- fixed documentation about viewModels. The key in xml should be view_m… (by @Jakhotiya) + * [magento/magento2#13904](https://github.com/magento/magento2/pull/13904) -- Add a link to the cart to the success message when adding a product (by @avstudnitz) + * [magento/magento2#14428](https://github.com/magento/magento2/pull/14428) -- Fix \Magento\Checkout\Controller\Index\Index::isSecureRequest method to take care of current request being secure and also from referer, as stated in phpdoc block (by @adrian-martinez-interactiv4) + * [magento/magento2#15129](https://github.com/magento/magento2/pull/15129) -- Add missing false-check to the ConfiguredRegularPrice price-model (by @tkotosz) + * [magento/magento2#15136](https://github.com/magento/magento2/pull/15136) -- [Backport] Add concrete type hints for product and category resources (by @rogyar) + * [magento/magento2#15162](https://github.com/magento/magento2/pull/15162) -- Fixed js error when product has double quote in its name (by @vovayatsyuk) + * [magento/magento2#15173](https://github.com/magento/magento2/pull/15173) -- Removed unused class declaration & code in comment (by @Yogeshks) + * [magento/magento2#15144](https://github.com/magento/magento2/pull/15144) -- Fixed Issue #11354 Merged CSS file name generation (by @sashas777) + * [magento/magento2#15174](https://github.com/magento/magento2/pull/15174) -- Restored app:config:status command after it was accidentally removed. (by @hostep) + * [magento/magento2#12324](https://github.com/magento/magento2/pull/12324) -- Simplified \Magento\Framework\Reflection\TypeProcessor (by @joni-jones) + * [magento/magento2#13185](https://github.com/magento/magento2/pull/13185) -- Fix negative basket total due to shipping tax residue (by @torreytsui) + * [magento/magento2#14614](https://github.com/magento/magento2/pull/14614) -- Add Parent Item to order item in repository (by @JeroenVanLeusden) + * [magento/magento2#15172](https://github.com/magento/magento2/pull/15172) -- 7399-clickableOverlay-less-fix - added pointer-events rule to .modal-… (by @virtua-pmakowski) + * [magento/magento2#15178](https://github.com/magento/magento2/pull/15178) -- Removed mageMenu widget dependency from breadcrumbs component (by @vovayatsyuk) + * [magento/magento2#15197](https://github.com/magento/magento2/pull/15197) -- Fix Magento_ImportExport not supporting unicode characters in column names (by @tdgroot) + * [magento/magento2#15202](https://github.com/magento/magento2/pull/15202) -- [Backport 2.2] Fix for displaying a negative price for a custom option. (by @dverkade) + * [magento/magento2#15137](https://github.com/magento/magento2/pull/15137) -- fix: do not forcefully set area in template if it is already set, fixes #14968, fixes #13530 (by @DanielRuf) + * [magento/magento2#15256](https://github.com/magento/magento2/pull/15256) -- Fixed typo in method name (by @olmer) + * [magento/magento2#14994](https://github.com/magento/magento2/pull/14994) -- Prevent not category links in breadcrumbs at product page (by @vovayatsyuk) + * [magento/magento2#15194](https://github.com/magento/magento2/pull/15194) -- Move buttons definition to separate file (by @jissereitsma) + * [magento/magento2#15249](https://github.com/magento/magento2/pull/15249) -- Removed non-existing argument (by @Yogeshks) + * [magento/magento2#15262](https://github.com/magento/magento2/pull/15262) -- Fix \Magento\Customer\Model\Customer\NotificationStorage class (by @adrian-martinez-interactiv4) + * [magento/magento2#15058](https://github.com/magento/magento2/pull/15058) -- Add 'const' type support to layout arguments (by @IgorVitol) + * [magento/magento2#15133](https://github.com/magento/magento2/pull/15133) -- Fix outdated address data when using Braintree's "Pay with PayPal" button (by @vovayatsyuk) + * [magento/magento2#15269](https://github.com/magento/magento2/pull/15269) -- Fix typo in Image::open exception message (by @t-richards) + * [magento/magento2#15282](https://github.com/magento/magento2/pull/15282) -- [fix] typo in private method name getUniq[ue]ImageIndex (by @mhauri) + * [magento/magento2#15291](https://github.com/magento/magento2/pull/15291) -- [Backport] Fix typo in database column comment (by @VitaliyBoyko) + * [magento/magento2#15292](https://github.com/magento/magento2/pull/15292) -- Fix typo in property name (by @dmytro-ch) + * [magento/magento2#15276](https://github.com/magento/magento2/pull/15276) -- [fix] typo in method name _getCharg[e]ableOptionPrice (by @mhauri) + * [magento/magento2#15293](https://github.com/magento/magento2/pull/15293) -- Fix typos in PHPDocs and comments (by @dmytro-ch) + * [magento/magento2#15302](https://github.com/magento/magento2/pull/15302) -- Fixed typo mistake in function comment (by @NamrataChangani) + * [magento/magento2#15294](https://github.com/magento/magento2/pull/15294) -- Fix typos in variable names (by @dmytro-ch) + * [magento/magento2#15386](https://github.com/magento/magento2/pull/15386) -- [Backport-2.2] Unused variable removed (by @VitaliyBoyko) + * [magento/magento2#15265](https://github.com/magento/magento2/pull/15265) -- declare var to fix scope error (by @keithbentrup) + * [magento/magento2#15333](https://github.com/magento/magento2/pull/15333) -- Added language translation for message string (by @Yogeshks) + * [magento/magento2#15331](https://github.com/magento/magento2/pull/15331) -- set alignment purchase order form and place order button (by @neeta-wagento) + * [magento/magento2#15341](https://github.com/magento/magento2/pull/15341) -- Refactored javascript code of admin notification modal popup (by @rahul-kachhadiya) + * [magento/magento2#15343](https://github.com/magento/magento2/pull/15343) -- Format the javascript code in Tax module (by @vgelani) + * [magento/magento2#15346](https://github.com/magento/magento2/pull/15346) -- Function is unnecessarily called multiple time (by @saurabh-aureate) + * [magento/magento2#15350](https://github.com/magento/magento2/pull/15350) -- 15319 : misleading data-container in product list (by @sunilit42) + * [magento/magento2#15351](https://github.com/magento/magento2/pull/15351) -- Refactor javascript code of button split widget (by @amittiwari024) + * [magento/magento2#15362](https://github.com/magento/magento2/pull/15362) -- Removed duplicate line and added comment on variable (by @vgelani) + * [magento/magento2#15411](https://github.com/magento/magento2/pull/15411) -- [Backport-2.2] Fixed abstract.js typo (by @VitaliyBoyko) + * [magento/magento2#15089](https://github.com/magento/magento2/pull/15089) -- Fix unnecessary recalculation of product list pricing (by @JeroenVanLeusden) + * [magento/magento2#15247](https://github.com/magento/magento2/pull/15247) -- ISSUE-14747 Newsletter subscription confirmation message does not dis… (by @KaushikChavda) + * [magento/magento2#15275](https://github.com/magento/magento2/pull/15275) -- [fix] typo in method name _exportAddress[s]es (by @mhauri) + * [magento/magento2#15332](https://github.com/magento/magento2/pull/15332) -- #14063 - Wrong invoice prefix in multistore setup due to default stor… (by @sanjay-wagento) + * [magento/magento2#15336](https://github.com/magento/magento2/pull/15336) -- #12820 - Wrong annotation in _toOptionArray - magento/framework/Data/… (by @sanjay-wagento) + * [magento/magento2#15347](https://github.com/magento/magento2/pull/15347) -- Fixed breadcrumb quote issue in product page #15037 (by @jignesh-baldha) + * [magento/magento2#15372](https://github.com/magento/magento2/pull/15372) -- Fixed Purchased Order Form button should visible properly (by @vgelani) + * [magento/magento2#15353](https://github.com/magento/magento2/pull/15353) -- Responsive Design Footers bottom of screen on mobile devices #15118 (by @chirag-wagento) + * [magento/magento2#15398](https://github.com/magento/magento2/pull/15398) -- Fixed set template syntax issue (by @vgelani) + * [magento/magento2#15431](https://github.com/magento/magento2/pull/15431) -- typo correction (by @AnshuMishra17) + * [magento/magento2#15010](https://github.com/magento/magento2/pull/15010) -- [BUGFIX] Added row_id to the flat action indexer so the value isn't s… (by @lewisvoncken) + * [magento/magento2#15211](https://github.com/magento/magento2/pull/15211) -- Error 500 in Module Manager (by @flancer64) + * [magento/magento2#15258](https://github.com/magento/magento2/pull/15258) -- [backport] fixes for instant purchase module from #15257 (by @mhauri) + * [magento/magento2#15340](https://github.com/magento/magento2/pull/15340) -- Submitting search form (mini) with enter key fires event handlers bound by jquery twice (by @amjadm61) + * [magento/magento2#15364](https://github.com/magento/magento2/pull/15364) -- Added language translation for comment tag (by @Yogeshks) + * [magento/magento2#15371](https://github.com/magento/magento2/pull/15371) -- Added language translation in template files (by @rahul-kachhadiya) + * [magento/magento2#15409](https://github.com/magento/magento2/pull/15409) -- Prevent multiple add-to-cart initializations in case of ajax loaded product listing (by @vovayatsyuk) + * [magento/magento2#15422](https://github.com/magento/magento2/pull/15422) -- Refactor JavsScript for UrlRewrite module edit page (by @patelnimesh1988) + * [magento/magento2#15421](https://github.com/magento/magento2/pull/15421) -- Updated font-size variable and standardize #ToDo UI (by @vgelani) + * [magento/magento2#15435](https://github.com/magento/magento2/pull/15435) -- [Backport] Removed redundant else statement (by @rogyar) + * [magento/magento2#15460](https://github.com/magento/magento2/pull/15460) -- Improvements to the CONTRIBUTING.md document (by @RebeccaBrocton) + * [magento/magento2#15040](https://github.com/magento/magento2/pull/15040) -- [2.2-develop] Transport variable can not be altered in email_invoice_set_template_vars_before Event (by @gwharton) + * [magento/magento2#15312](https://github.com/magento/magento2/pull/15312) -- [Fix] forgot to add lowercase conversion on grouped product assignation (by @jalogut) + * [magento/magento2#15454](https://github.com/magento/magento2/pull/15454) -- Fix HTML syntax in report.phtml error template (by @abcpremium) + * [magento/magento2#15416](https://github.com/magento/magento2/pull/15416) -- Moved css from media #TODO (by @vgelani) + * [magento/magento2#15462](https://github.com/magento/magento2/pull/15462) -- Adding manners to GitHub templates 2.2 (by @dmanners) + * [magento/magento2#15511](https://github.com/magento/magento2/pull/15511) -- Fixes in config module (by @mhauri) + * [magento/magento2#15513](https://github.com/magento/magento2/pull/15513) -- Fix typos in Multishipping and User module (by @avoelkl) + * [magento/magento2#15301](https://github.com/magento/magento2/pull/15301) -- Refactor JavsScript for customer logout (by @patelnimesh1988) + * [magento/magento2#15382](https://github.com/magento/magento2/pull/15382) -- Fix for Magnifier in inside mode (by @kacperchara) + * [magento/magento2#15499](https://github.com/magento/magento2/pull/15499) -- Issue 15469: Javascript error dropdowns.js (by @brian-labelle) + * [magento/magento2#15512](https://github.com/magento/magento2/pull/15512) -- Fixes in ui module (by @mhauri) + * [magento/magento2#15515](https://github.com/magento/magento2/pull/15515) -- [fix] dynamical assigned property in webapi (by @mhauri) + * [magento/magento2#15459](https://github.com/magento/magento2/pull/15459) -- [Resolved : UI Component listing action column outside of screen when… (by @hitesh-wagento) + * [magento/magento2#15468](https://github.com/magento/magento2/pull/15468) -- Issue 15467 where a configuration sku gets deleted but is still saved… (by @jonshipman) + * [magento/magento2#15514](https://github.com/magento/magento2/pull/15514) -- Fix method name (typo) (by @avoelkl) + * [magento/magento2#15517](https://github.com/magento/magento2/pull/15517) -- Use stored value of method instead of calling same method again. (by @saurabh-aureate) + * [magento/magento2#15519](https://github.com/magento/magento2/pull/15519) -- Typo correction (by @saurabh-aureate) + * [magento/magento2#15552](https://github.com/magento/magento2/pull/15552) -- Remove extra space and format the code in translation file (by @saurabh-aureate) + * [magento/magento2#15549](https://github.com/magento/magento2/pull/15549) -- Fixed typo error (by @vgelani) + * [magento/magento2#15097](https://github.com/magento/magento2/pull/15097) -- Add field to filter to collection (by @dverkade) + * [magento/magento2#15305](https://github.com/magento/magento2/pull/15305) -- chore: remove extraneous cursor property (by @DanielRuf) + * [magento/magento2#15477](https://github.com/magento/magento2/pull/15477) -- Variant product image in sidebar wishlist block (by @kishanpatadia) + * [magento/magento2#15598](https://github.com/magento/magento2/pull/15598) -- [BUGFIX] #15564 Generated admin API token expires immediately (by @krukas) + * [magento/magento2#15602](https://github.com/magento/magento2/pull/15602) -- set correct annotation (by @sanjay-wagento) + * [magento/magento2#13328](https://github.com/magento/magento2/pull/13328) -- Add indexes to timestamp field in oauth_nonce (by @KarlDeux) + * [magento/magento2#15349](https://github.com/magento/magento2/pull/15349) -- Resolve Knockout non-unique elements id in console error (by @neeta-wagento) + * [magento/magento2#15594](https://github.com/magento/magento2/pull/15594) -- Remove extra semicolon from the files (by @saurabh-aureate) + * [magento/magento2#15585](https://github.com/magento/magento2/pull/15585) -- Fix #13415 : Duplicated elements id (by @julienanquetil) + * [magento/magento2#15615](https://github.com/magento/magento2/pull/15615) -- [Backport] Removed comma(,) from translate attribute (by @dmytro-ch) + * [magento/magento2#15621](https://github.com/magento/magento2/pull/15621) -- fix typo for setCateroryIds (by @neeta-wagento) + * [magento/magento2#15645](https://github.com/magento/magento2/pull/15645) -- [Resolved : Menu widget submenu alignment #7897] (by @hitesh-wagento) + * [magento/magento2#15566](https://github.com/magento/magento2/pull/15566) -- Fixxes #15565 (by @EliasKotlyar) + * [magento/magento2#15715](https://github.com/magento/magento2/pull/15715) -- [BACKPORT 2.2 #15695] Fixed a couple of typos (by @dverkade) + * [magento/magento2#15718](https://github.com/magento/magento2/pull/15718) -- [Backport 2.2] Fixed return type of wishlist's getImageData in DocBlock (by @rogyar) + * [magento/magento2#15365](https://github.com/magento/magento2/pull/15365) -- Trim username on customer account login page (by @dankhrapiyush) + * [magento/magento2#15485](https://github.com/magento/magento2/pull/15485) -- fix: support multiple minisearch widget instances (by @DanielRuf) + * [magento/magento2#15614](https://github.com/magento/magento2/pull/15614) -- [Backport] Fixed product tier pricing pagination issue in admin (by @dmytro-ch) + * [magento/magento2#15765](https://github.com/magento/magento2/pull/15765) -- check if order data is available to incl ec (by @torhoehn) + * [magento/magento2#12314](https://github.com/magento/magento2/pull/12314) -- Prevent layout cache corruption in edge case (by @scottsb) + * [magento/magento2#15539](https://github.com/magento/magento2/pull/15539) -- FIX fo rissue #15510 - First PDF download / export after login (by @phoenix128) + * [magento/magento2#15694](https://github.com/magento/magento2/pull/15694) -- [Backport 2.2] Fix minor issues in ui export converter classes (by @dmytro-ch) + * [magento/magento2#15734](https://github.com/magento/magento2/pull/15734) -- [Resolved : Styling select by changing less variables in Luma theme (by @hitesh-wagento) + * [magento/magento2#15782](https://github.com/magento/magento2/pull/15782) -- [Backport 2.2]Fix translations (by @VitaliyBoyko) + * [magento/magento2#15791](https://github.com/magento/magento2/pull/15791) -- Removed unused class from forms less file. (by @chirag-wagento) + * [magento/magento2#15789](https://github.com/magento/magento2/pull/15789) -- Removed unnecessary css. (by @chirag-wagento) + * [magento/magento2#15795](https://github.com/magento/magento2/pull/15795) -- Remove double semicolon from the style sheets. (by @NamrataChangani) + * [magento/magento2#15825](https://github.com/magento/magento2/pull/15825) -- Fixed set template syntax issue (by @NamrataChangani) + * [magento/magento2#15840](https://github.com/magento/magento2/pull/15840) -- [Backport] Fix for issue 911 found on MSI project - Cannot read property source_… #14 (by @chirag-wagento) + * [magento/magento2#15854](https://github.com/magento/magento2/pull/15854) -- [Backport 2.2] Fixed return type hinting in DocBlocks for Wishlist module (by @rogyar) + * [magento/magento2#15871](https://github.com/magento/magento2/pull/15871) -- chore: remove unused less import (by @DanielRuf) + * [magento/magento2#12626](https://github.com/magento/magento2/pull/12626) -- Fixed condition with usage "hack" isPostRequest method (by @pusachev) + * [magento/magento2#12935](https://github.com/magento/magento2/pull/12935) -- Add Ability To Separate Frontend / Adminhtml in New Relic (by @mpchadwick) + * [magento/magento2#15019](https://github.com/magento/magento2/pull/15019) -- [TASK] Solve issue #14966 - Disabling product does not remove it from… (by @lewisvoncken) + * [magento/magento2#15297](https://github.com/magento/magento2/pull/15297) -- Fix typo in test method's name and test result (by @dmytro-ch) + * [magento/magento2#15320](https://github.com/magento/magento2/pull/15320) -- issue/14056 - Coupon API not working for guest user (by @Hypo386) + * [magento/magento2#15322](https://github.com/magento/magento2/pull/15322) -- ISSUE-11477 - fixed Swagger response for searchCriteria (by @idziakjakub) + * [magento/magento2#15661](https://github.com/magento/magento2/pull/15661) -- Fixed Wrong order amount on dashboard on Last orders listing when having more than one website with different currencies (by @ankurvr) + * [magento/magento2#15689](https://github.com/magento/magento2/pull/15689) -- #15588 Fixed incorrect image urls in multistore xml sitemap (by @StevenGuapaBV) + * [magento/magento2#15826](https://github.com/magento/magento2/pull/15826) -- Add missing table aliases to fields mapping for Customer Group filter… (by @Radio) + * [magento/magento2#13862](https://github.com/magento/magento2/pull/13862) -- Add compare list link to success message after adding a product (by @avstudnitz) + * [magento/magento2#15888](https://github.com/magento/magento2/pull/15888) -- Correct typo correction js files (by @saurabh-aureate) + * [magento/magento2#15892](https://github.com/magento/magento2/pull/15892) -- Wrong annotation in _toOptionArray : lib\internal\Magento\Framework\D… (by @NamrataChangani) + * [magento/magento2#15891](https://github.com/magento/magento2/pull/15891) -- Remove parameter from method calling (by @saurabh-aureate) + * [magento/magento2#15878](https://github.com/magento/magento2/pull/15878) -- [Resolved : limiter float too generic] (by @hitesh-wagento) + * [magento/magento2#15907](https://github.com/magento/magento2/pull/15907) -- fixed word typo (by @ledian-hymetllari) + * [magento/magento2#15914](https://github.com/magento/magento2/pull/15914) -- [Resolved : Changing @tab-content__border variable has no effect in B… (by @hitesh-wagento) + * [magento/magento2#15936](https://github.com/magento/magento2/pull/15936) -- #15308 removed extraneous margin (by @StevenGuapaBV) + * [magento/magento2#15991](https://github.com/magento/magento2/pull/15991) -- fix for dropdown toggle icon in cart (by @Karlasa) + * [magento/magento2#16001](https://github.com/magento/magento2/pull/16001) -- Extend default config instead overwrite (by @likemusic) + * [magento/magento2#16002](https://github.com/magento/magento2/pull/16002) -- bugfix checkout page cart icon color (by @Karlasa) + * [magento/magento2#16023](https://github.com/magento/magento2/pull/16023) -- [Backport 2.2] Wishlist: Remove unnecessary parameter from invoking toHtml() method (by @rogyar) + * [magento/magento2#16048](https://github.com/magento/magento2/pull/16048) -- fix: prevent inline-block issue in name form due to space and font-size (by @DanielRuf) + * [magento/magento2#15647](https://github.com/magento/magento2/pull/15647) -- [Backport] Fixes in widget component (by @mhauri) + * [magento/magento2#15811](https://github.com/magento/magento2/pull/15811) -- [Correct code formatting] (by @hitesh-wagento) + * [magento/magento2#15893](https://github.com/magento/magento2/pull/15893) -- Solve overlapping Issue on every Home page & category page of Hot Sel… (by @chirag-wagento) + * [magento/magento2#15902](https://github.com/magento/magento2/pull/15902) -- Complete the fix for cache issue due to the currencies with no symbol (by @dmytro-ch) + * [magento/magento2#15913](https://github.com/magento/magento2/pull/15913) -- [Backport] Fixes in catalog component blocks [2.3-develop] (by @chirag-wagento) + * [magento/magento2#16012](https://github.com/magento/magento2/pull/16012) -- Fix issue #15832 (by @Karlasa) + * [magento/magento2#16010](https://github.com/magento/magento2/pull/16010) -- Small refactoring to better code readability (by @likemusic) + * [magento/magento2#16053](https://github.com/magento/magento2/pull/16053) -- Improve retrieval of first array element (by @thomas-blackbird) + * [magento/magento2#16052](https://github.com/magento/magento2/pull/16052) -- Disabling sorting in glob and scandir functions for better performance (by @lfluvisotto) + * [magento/magento2#16065](https://github.com/magento/magento2/pull/16065) -- [Backport 2.2] Added unit test for captcha string resolver (by @rogyar) + * [magento/magento2#16080](https://github.com/magento/magento2/pull/16080) -- Adding support for variadic arguments fro method in generated proxy c… (by @vgelani) + * [magento/magento2#15344](https://github.com/magento/magento2/pull/15344) -- FIXED - appended payment code to ID field to make it unique (by @rakesh-gangani) + * [magento/magento2#15534](https://github.com/magento/magento2/pull/15534) -- magento/magento2#15255 unlock customer after password reset (by @andreagaspardo) + * [magento/magento2#15604](https://github.com/magento/magento2/pull/15604) -- [Backport] Removed unused translation for comment tag (by @rogyar) + * [magento/magento2#15870](https://github.com/magento/magento2/pull/15870) -- chore: prefer woff and woff2 (by @DanielRuf) + * [magento/magento2#15272](https://github.com/magento/magento2/pull/15272) -- DOBISSUE date format changed after customer tries to register with sa… (by @KaushikChavda) + * [magento/magento2#15993](https://github.com/magento/magento2/pull/15993) -- Correct return type of methods and typo correction. (by @saurabh-aureate) + * [magento/magento2#16082](https://github.com/magento/magento2/pull/16082) -- Navigation dropdown caret icon. (by @tejash-wagento) + * [magento/magento2#16091](https://github.com/magento/magento2/pull/16091) -- Replaced @escapeNotVerified annotations (by @istiahailo) + * [magento/magento2#16144](https://github.com/magento/magento2/pull/16144) -- array_push(...) calls behaving as '$array[] = ...', $array[] = works faster than invoking functions in PHP (by @lfluvisotto) + * [magento/magento2#16160](https://github.com/magento/magento2/pull/16160) -- [Backport 2.2] Captcha: Added unit test for CheckRegisterCheckoutObserver (by @rogyar) + * [magento/magento2#16181](https://github.com/magento/magento2/pull/16181) -- Fixed syntax for before-after operators in less files. (by @NamrataChangani) + * [magento/magento2#16182](https://github.com/magento/magento2/pull/16182) -- Removed double occurrence of keywords from sentences. (by @NamrataChangani) + * [magento/magento2#16183](https://github.com/magento/magento2/pull/16183) -- Correct sentence in comment section in class file. (by @NamrataChangani) + * [magento/magento2#16190](https://github.com/magento/magento2/pull/16190) -- #16079 translation possibility for moreButtonText (by @Karlasa) + * [magento/magento2#16194](https://github.com/magento/magento2/pull/16194) -- magento/magento2#16184: Fix type error in payment void method (by @xpoback) + * [magento/magento2#16192](https://github.com/magento/magento2/pull/16192) -- Trim email address in customer account create and login form (by @dankhrapiyush) + * [magento/magento2#16206](https://github.com/magento/magento2/pull/16206) -- Declare module namespace before template path name(Magento_Sales::order/info.phtml). (by @ronak2ram) + * [magento/magento2#16211](https://github.com/magento/magento2/pull/16211) -- Setting deploy mode to production with --skip-compilation flag should not clear generated code (by @platformvaimo) + * [magento/magento2#16213](https://github.com/magento/magento2/pull/16213) -- Fix for #8222 (by @0m3r) + * [magento/magento2#16216](https://github.com/magento/magento2/pull/16216) -- 15863: [Forwardport] Refactored javascript code of admin notification modal popup (by @IvanPletnyov) + * [magento/magento2#16220](https://github.com/magento/magento2/pull/16220) -- Incorrect value NULL was passed to DataObject constructor. It caused … (by @Jakhotiya) + * [magento/magento2#16230](https://github.com/magento/magento2/pull/16230) -- Correct spelling mistakes in Model and library files. (by @NamrataChangani) + * [magento/magento2#15521](https://github.com/magento/magento2/pull/15521) -- Move breadcrumb json configuration to viewmodel (by @diedburn) + * [magento/magento2#15532](https://github.com/magento/magento2/pull/15532) -- FIX for issue #15501 - M2.2.4 missing meta title tag and doesn't show… (by @phoenix128) + * [magento/magento2#15629](https://github.com/magento/magento2/pull/15629) -- Fix #15627: Product order in category changed in Magento 2.2.4 (by @dverkade) + * [magento/magento2#15637](https://github.com/magento/magento2/pull/15637) -- Create ability to set is_visible_on_front to order status history comment (by @markoshust) + * [magento/magento2#16141](https://github.com/magento/magento2/pull/16141) -- Fix case mismatch call (class/method) (by @lfluvisotto) + * [magento/magento2#16215](https://github.com/magento/magento2/pull/16215) -- PHPDoc (by @lfluvisotto) + * [magento/magento2#16247](https://github.com/magento/magento2/pull/16247) -- Fixed typo error (by @vgelani) + * [magento/magento2#16240](https://github.com/magento/magento2/pull/16240) -- Removed double occurrence of 'it' from sentences. (by @NamrataChangani) + * [magento/magento2#16250](https://github.com/magento/magento2/pull/16250) -- Update Israeli ZIP code mask: 7 digits instead of 5 (by @itaymesh) + * [magento/magento2#12771](https://github.com/magento/magento2/pull/12771) -- magento/magento2#12695: Unable to change attribute type from swatch (by @eugene-shab) + * [magento/magento2#15774](https://github.com/magento/magento2/pull/15774) -- [Backport] Fix issue #14895 - Change Password warning message appear two times (by @sanjay-wagento) + * [magento/magento2#15872](https://github.com/magento/magento2/pull/15872) -- Fix missing PHPDocs hinting for AdvancedPricingImportExport module (by @dmytro-ch) + * [magento/magento2#15845](https://github.com/magento/magento2/pull/15845) -- Update webapi.xml to fix typo (by @mhaack) + * [magento/magento2#15929](https://github.com/magento/magento2/pull/15929) -- Postpone instantiation of session config by using a proxy (by @fmarangi) + * [magento/magento2#16286](https://github.com/magento/magento2/pull/16286) -- Add UpdatedAtListProvider to NotSyncedDataProvider for invoice grid (by @JeroenVanLeusden) + * [magento/magento2#16300](https://github.com/magento/magento2/pull/16300) -- Captcha: Added integration test for checking admin login attempts cleanup (by @rogyar) + * [magento/magento2#16306](https://github.com/magento/magento2/pull/16306) -- Captcha: Added integration tests for checking customer login attempts cleanup (by @rogyar) + * [magento/magento2#13509](https://github.com/magento/magento2/pull/13509) -- Use constant time string comparison in FormKey validator (by @p0pr0ck5) + * [magento/magento2#15339](https://github.com/magento/magento2/pull/15339) -- Fixed set template syntax in block file (by @NamrataChangani) + * [magento/magento2#16093](https://github.com/magento/magento2/pull/16093) -- When searching for the title if search for all the segments that has … (by @rsantellan) + * [magento/magento2#16217](https://github.com/magento/magento2/pull/16217) -- Admin controller product set save refactor (by @AnshuMishra17) + * [magento/magento2#16279](https://github.com/magento/magento2/pull/16279) -- MAGETWO-61209: Backport - Fixed issue #7379 with mage/calendar when setting `numberOfM… (by @vasilii-b) + * [magento/magento2#16333](https://github.com/magento/magento2/pull/16333) -- Add metadata title in unit test (by @slackerzz) + * [magento/magento2#16379](https://github.com/magento/magento2/pull/16379) -- [Changed password placeholder text in checkout page] (by @hitesh-wagento) + * [magento/magento2#16389](https://github.com/magento/magento2/pull/16389) -- [Backport 2.2] Use correct error message for duplicate error key in product import (by @gelanivishal) + * [magento/magento2#15464](https://github.com/magento/magento2/pull/15464) -- Fix "Confirmation request" email is sent on customer's newsletter unsubscribe action (by @nuzil) + * [magento/magento2#16009](https://github.com/magento/magento2/pull/16009) -- fix: change "My Dashboard" to "My Account", fixes #16007 (by @DanielRuf) + * [magento/magento2#16086](https://github.com/magento/magento2/pull/16086) -- Fix false cache_lifetime usage in xml layouts (by @yuriyDne) + * [magento/magento2#16372](https://github.com/magento/magento2/pull/16372) -- Wishlist update item issue (by @eduard13) + * [magento/magento2#16386](https://github.com/magento/magento2/pull/16386) -- Login with wishlist raise report after logout. (by @swnsma) + * [magento/magento2#16438](https://github.com/magento/magento2/pull/16438) -- Credit memo email template file: fixing incorrect object type error (by @JosephMaxwell) + * [magento/magento2#16458](https://github.com/magento/magento2/pull/16458) -- Add missing showInStore attributes (by @aschrammel) + * [magento/magento2#16477](https://github.com/magento/magento2/pull/16477) -- Fix for #14593 (second try #16431) (by @0m3r) + * [magento/magento2#15543](https://github.com/magento/magento2/pull/15543) -- Enhancements to module:status command (by @jissereitsma) + * [magento/magento2#16472](https://github.com/magento/magento2/pull/16472) -- Improve comment message (by @gelanivishal) + * [magento/magento2#16489](https://github.com/magento/magento2/pull/16489) -- Properly hyphenate "third-party" (by @erikhansen) + * [magento/magento2#16495](https://github.com/magento/magento2/pull/16495) -- Fixed spell issue in library (by @sanganinamrata) + * [magento/magento2#15909](https://github.com/magento/magento2/pull/15909) -- [Backport] Fix for Wrong price amount on product page (by @gelanivishal) + * [magento/magento2#16090](https://github.com/magento/magento2/pull/16090) -- Added and removed unnecessary translation for label/comment tags (by @Yogeshks) + * [magento/magento2#16393](https://github.com/magento/magento2/pull/16393) -- Rework for PR #16222 . (by @phoenix128) + * [magento/magento2#16517](https://github.com/magento/magento2/pull/16517) -- Fix responsive tables showing broken heading (by @LordZardeck) + * [magento/magento2#16524](https://github.com/magento/magento2/pull/16524) -- Clear converted file data (by @gelanivishal) + * [magento/magento2#16549](https://github.com/magento/magento2/pull/16549) -- Corrected function comment (by @sanganinamrata) + * [magento/magento2#16553](https://github.com/magento/magento2/pull/16553) -- Update mini-cart checkout translations (by @JeroenVanLeusden) + * [magento/magento2#16557](https://github.com/magento/magento2/pull/16557) -- Updated SynonymGroup.xml (by @sanganinamrata) + * [magento/magento2#16576](https://github.com/magento/magento2/pull/16576) -- [Backport] Declare module namespace before template path name (by @mageprince) + * [magento/magento2#16581](https://github.com/magento/magento2/pull/16581) -- Removed double occurrences from files. (by @sanganinamrata) + * [magento/magento2#16143](https://github.com/magento/magento2/pull/16143) -- Variable as a method parameter might be overridden by the loop (by @lfluvisotto) + * [magento/magento2#16254](https://github.com/magento/magento2/pull/16254) -- Customer group extension attributes not carried over on save (by @JosephMaxwell) + * [magento/magento2#16474](https://github.com/magento/magento2/pull/16474) -- [FIX] dev:di:info duplicates plugin info (by @Coderimus) + * [magento/magento2#16564](https://github.com/magento/magento2/pull/16564) -- Trim email address in newsletter, forgot password, checkout login and email to a friend form (by @dankhrapiyush) + * [magento/magento2#11554](https://github.com/magento/magento2/pull/11554) -- Improve attribute checking (by @FreekVandeursen) + * [magento/magento2#16271](https://github.com/magento/magento2/pull/16271) -- Covered Magento\Checkout\Model\Cart\CollectQuote by Unit Test (by @eduard13) + * [magento/magento2#16540](https://github.com/magento/magento2/pull/16540) -- Fix zero price simple failed to resolve as default (by @torreytsui) + * [magento/magento2#16530](https://github.com/magento/magento2/pull/16530) -- Fixed widget template rendering issue while rewriting widget block. (by @sanganinamrata) + * [magento/magento2#16559](https://github.com/magento/magento2/pull/16559) -- fix icon color variable naming (by @Karlasa) + * [magento/magento2#16584](https://github.com/magento/magento2/pull/16584) -- [Backport] Remove the timezone from the date when retrieving the current month from a UTC timestamp. (by @mageprince) + * [magento/magento2#16590](https://github.com/magento/magento2/pull/16590) -- Fix of invalid price for integer currencies when amount less than group size (by @vkublytskyi) + * [magento/magento2#16626](https://github.com/magento/magento2/pull/16626) -- [Backport] Fix type hints and add undefined property in Webapi [2.3-develop] (by @mageprince) + * [magento/magento2#16644](https://github.com/magento/magento2/pull/16644) -- Removed double occurrences from Magento modules. (by @sanganinamrata) + * [magento/magento2#16645](https://github.com/magento/magento2/pull/16645) -- Updated Magento_Newsletter's block file. (by @sanganinamrata) + * [magento/magento2#16646](https://github.com/magento/magento2/pull/16646) -- Corrected Magento_Framework's test xml file. (by @sanganinamrata) + * [magento/magento2#16669](https://github.com/magento/magento2/pull/16669) -- Prevent servers being slammed from many search suggestion requests (by @LordZardeck) + * [magento/magento2#16678](https://github.com/magento/magento2/pull/16678) -- Improved code and remove unnecessary space (by @ronak2ram) + * [magento/magento2#16675](https://github.com/magento/magento2/pull/16675) -- Prevent running SQL query on every item in the database when the quote is empty (by @LordZardeck) + * [magento/magento2#16689](https://github.com/magento/magento2/pull/16689) -- Added 'title' attribute to 'a' link. (by @sanganinamrata) + * [magento/magento2#16690](https://github.com/magento/magento2/pull/16690) -- Added translation function for Magento_Braintree module's template file. (by @sanganinamrata) + * [magento/magento2#16691](https://github.com/magento/magento2/pull/16691) -- Added 'title' attribute to 'img' tag in knockout template files. (by @sanganinamrata) + * [magento/magento2#16711](https://github.com/magento/magento2/pull/16711) -- Fixed typo in SynonymGroupRepositoryInterface (by @AnshuMishra17) + * [magento/magento2#15479](https://github.com/magento/magento2/pull/15479) -- Fix newsletter subscription behaviour for registered customer. (by @nuzil) + * [magento/magento2#16175](https://github.com/magento/magento2/pull/16175) -- Admin tabs order not working properly (by @tiagosampaio) + * [magento/magento2#16408](https://github.com/magento/magento2/pull/16408) -- Fixed type hints and docs for Downloadable Samples block (by @phoenix-bjoern) + * [magento/magento2#16414](https://github.com/magento/magento2/pull/16414) -- Fixing a Mistype Error (by @tiagosampaio) + * [magento/magento2#16554](https://github.com/magento/magento2/pull/16554) -- Fix docBlock for hasInvoices(), hasShipments(), hasCreditmemos() (by @nuzil) + * [magento/magento2#16566](https://github.com/magento/magento2/pull/16566) -- Smallest codestyle fix in Option/Type/Text.php (by @likemusic) + * [magento/magento2#16680](https://github.com/magento/magento2/pull/16680) -- Captcha: Added unit test for CheckGuestCheckoutObserver (by @rogyar) + * [magento/magento2#16693](https://github.com/magento/magento2/pull/16693) -- 'Allowed Countries' - get countries for scope 'default'. (by @swnsma) + * [magento/magento2#16685](https://github.com/magento/magento2/pull/16685) -- Updated security issues details (by @quisse) + * [magento/magento2#16704](https://github.com/magento/magento2/pull/16704) -- Add sort order to user agent rules table headers (by @JRhyne) + * [magento/magento2#16717](https://github.com/magento/magento2/pull/16717) -- Removed space before ending sentence. (by @sanganinamrata) + * [magento/magento2#16716](https://github.com/magento/magento2/pull/16716) -- fix _utilities.less font-size issue (by @Karlasa) + * [magento/magento2#16721](https://github.com/magento/magento2/pull/16721) -- Corrected return message from ProductRuleTest.php (by @sanganinamrata) + * [magento/magento2#16732](https://github.com/magento/magento2/pull/16732) -- Resolved : no navigation-level0-item__hover__color #15848 (by @hitesh-wagento) + * [magento/magento2#16726](https://github.com/magento/magento2/pull/16726) -- [Backport 2.3] Add spelling correction: formatedPrice to formattedPrice (by @arnoudhgz) + * [magento/magento2#13569](https://github.com/magento/magento2/pull/13569) -- Correctly save Product Custom Option values (by @JeroenVanLeusden) + * [magento/magento2#14379](https://github.com/magento/magento2/pull/14379) -- [Backport 2.2] Issue 14351: Product import doesn't change `Enable Qty Increments` field (by @simpleadm) + * [magento/magento2#16599](https://github.com/magento/magento2/pull/16599) -- Fixed backwards incompatible change to Transport variable event parameters (by @gwharton) + * [magento/magento2#16748](https://github.com/magento/magento2/pull/16748) -- Remove commented code & remove space (by @ronak2ram) + * [magento/magento2#16766](https://github.com/magento/magento2/pull/16766) -- fix #16764 Rating Star issue on Product detail Page. (by @Karlasa) + * [magento/magento2#16821](https://github.com/magento/magento2/pull/16821) -- Code improvement (by @mage2pratik) + * [magento/magento2#16831](https://github.com/magento/magento2/pull/16831) -- [Backport] Magento_Sales integration tests: fix invoice_list fixture var tags (by @ronak2ram) + * [magento/magento2#16435](https://github.com/magento/magento2/pull/16435) -- Add generated code to the psr-0 autoloader section so when optimizing… (by @hostep) + * [magento/magento2#16595](https://github.com/magento/magento2/pull/16595) -- Trim issue on customer confirmation form (by @gelanivishal) + * [magento/magento2#16845](https://github.com/magento/magento2/pull/16845) -- [Backport] Add @api annotation to Filter Group & Sort Order (by @ronak2ram) + * [magento/magento2#16861](https://github.com/magento/magento2/pull/16861) -- Add Confirm Modal Width (by @hryvinskyi) + * [magento/magento2#16872](https://github.com/magento/magento2/pull/16872) -- Remove extra spaces from Magento/Ui (by @ronak2ram) + * [magento/magento2#16873](https://github.com/magento/magento2/pull/16873) -- Improve "Invalid country code" error message on tax import (by @adampmoss) + * [magento/magento2#16916](https://github.com/magento/magento2/pull/16916) -- [Backport] Issue 5316 (by @ronak2ram) + * [magento/magento2#16579](https://github.com/magento/magento2/pull/16579) -- removed _responsive.less import from gallery.less (by @Karlasa) + * [magento/magento2#16707](https://github.com/magento/magento2/pull/16707) -- Update regex in ControllerAclTest (by @aleron75) + * [magento/magento2#16785](https://github.com/magento/magento2/pull/16785) -- Avoid undefined index warning when using uppercase reserved word (by @FreekVandeursen) + * [magento/magento2#16841](https://github.com/magento/magento2/pull/16841) -- Clean code (by @GraysonChiang) + * [magento/magento2#16840](https://github.com/magento/magento2/pull/16840) -- Log when Magento is in maintenance mode (by @Ethan3600) + * [magento/magento2#16851](https://github.com/magento/magento2/pull/16851) -- Remove direct use of object manager (by @AnshuMishra17) + * [magento/magento2#16882](https://github.com/magento/magento2/pull/16882) -- Remove duplicated string. (by @likemusic) + * [magento/magento2#16880](https://github.com/magento/magento2/pull/16880) -- Array short syntax (by @lfluvisotto) + * [magento/magento2#16889](https://github.com/magento/magento2/pull/16889) -- Microrefactoring in product gallery block helper (by @likemusic) + * [magento/magento2#16891](https://github.com/magento/magento2/pull/16891) -- Remove commented code (by @mage2pratik) + * [magento/magento2#16890](https://github.com/magento/magento2/pull/16890) -- hide cookie notice instead of reloading site (by @torhoehn) + * [magento/magento2#16899](https://github.com/magento/magento2/pull/16899) -- Fixing annotations for some methods. (by @tiagosampaio) + * [magento/magento2#16903](https://github.com/magento/magento2/pull/16903) -- Fixes white color coding standard. (by @chirag-wagento) + * [magento/magento2#16924](https://github.com/magento/magento2/pull/16924) -- Replacing Usage of Deprecated Methods for Message Manager. (by @tiagosampaio) + * [magento/magento2#16937](https://github.com/magento/magento2/pull/16937) -- Revert changing file permissions in #15144 (by @ihor-sviziev) + * [magento/magento2#16928](https://github.com/magento/magento2/pull/16928) -- Reduce lengthy code of LoginPost (by @GlennCheng) + * [magento/magento2#16978](https://github.com/magento/magento2/pull/16978) -- Wrong namespace defined in compare.phtml (by @ronak2ram) + * [magento/magento2#16977](https://github.com/magento/magento2/pull/16977) -- Removed double occurrences from Magento modules (by @mage2pratik) + * [magento/magento2#16980](https://github.com/magento/magento2/pull/16980) -- Fixed a couple of spelling mistakes (by @mage2pratik) + * [magento/magento2#17002](https://github.com/magento/magento2/pull/17002) -- [Backport] Remove unused comments from _initDiscount() function (by @mageprince) + * [magento/magento2#16560](https://github.com/magento/magento2/pull/16560) -- Admin user auth controller refactor (by @AnshuMishra17) + * [magento/magento2#16863](https://github.com/magento/magento2/pull/16863) -- Configurable Product with Only Size Options (No Color Options) Shows … (by @ronak2ram) + * [magento/magento2#16883](https://github.com/magento/magento2/pull/16883) -- Update nginx.config.sample to exclude php5-fpm (by @sean-wcb) + * [magento/magento2#16900](https://github.com/magento/magento2/pull/16900) -- Add Clean Code (by @hryvinskyi) + * [magento/magento2#16921](https://github.com/magento/magento2/pull/16921) -- Slight Changes to Code (by @tiagosampaio) + * [magento/magento2#16946](https://github.com/magento/magento2/pull/16946) -- Delete all unused imports of lib/internal/Magento (by @osrecio) + * [magento/magento2#16965](https://github.com/magento/magento2/pull/16965) -- fix: add hasrequired notice for create account form and password forg… (by @DanielRuf) + * [magento/magento2#17019](https://github.com/magento/magento2/pull/17019) -- Fixes Black color coding standard. (by @chirag-wagento) + * [magento/magento2#16952](https://github.com/magento/magento2/pull/16952) -- Issue 8131 - Use Redirect Factory to Allow Error Message Display on Advanced Search (by @brobie) + * [magento/magento2#16959](https://github.com/magento/magento2/pull/16959) -- Resolved : Mobile device style groups incorrect order (by @tejash-wagento) + * [magento/magento2#16971](https://github.com/magento/magento2/pull/16971) -- Fix misprint ('_requesetd' > '_requested') (by @likemusic) + * [magento/magento2#16988](https://github.com/magento/magento2/pull/16988) -- Correct return type of methods (by @mage2pratik) + * [magento/magento2#16984](https://github.com/magento/magento2/pull/16984) -- Categories > Left menu > Item title space fix (by @rafaelstz) + * [magento/magento2#17006](https://github.com/magento/magento2/pull/17006) -- Adjust page-main container height for sticky footer; fixes #15118 (by @denistrator) + * [magento/magento2#17027](https://github.com/magento/magento2/pull/17027) -- Remove spaces around amount span. (by @likemusic) + * [magento/magento2#17063](https://github.com/magento/magento2/pull/17063) -- Added unit test for DB model in backup module (by @rogyar) + * [magento/magento2#17077](https://github.com/magento/magento2/pull/17077) -- Remove commented code (by @mage2pratik) + * [magento/magento2#17097](https://github.com/magento/magento2/pull/17097) -- [Backport] Magento UI - Cleanup of undefined mixins parameters and usage of "leaking" variables scope (by @mageprince) + * [magento/magento2#17099](https://github.com/magento/magento2/pull/17099) -- [Backport] Fix app/code/Magento/Backend/Block/Media/Uploader.php getConfigJson() method (by @mageprince) + * [magento/magento2#17098](https://github.com/magento/magento2/pull/17098) -- [Backport] testGetIgnoresFirstSlash method in ObjectManagerTest has lost its purpose (dummy test) (by @mageprince) + * [magento/magento2#17114](https://github.com/magento/magento2/pull/17114) -- Disable autocomplete for captcha inputs (by @denistrator) + * [magento/magento2#17129](https://github.com/magento/magento2/pull/17129) -- Update template.js (by @angelomaragna) + * [magento/magento2#17137](https://github.com/magento/magento2/pull/17137) -- GoogleAnalytics: Added unit test for order success observer (by @rogyar) + * [magento/magento2#17151](https://github.com/magento/magento2/pull/17151) -- Fixed a grammatical error on the vault tooltip (by @kreativedev) + * [magento/magento2#15687](https://github.com/magento/magento2/pull/15687) -- Fixes for #15393 (by @simonjanguapa) + * [magento/magento2#16401](https://github.com/magento/magento2/pull/16401) -- Remove PDF files after generation (by @rogyar) + * [magento/magento2#16468](https://github.com/magento/magento2/pull/16468) -- #16273: Fix bug in method getUrlInStore() of product model (by @vasilii-b) + * [magento/magento2#17124](https://github.com/magento/magento2/pull/17124) -- Using interface instead of model directly (by @woutersamaey) + * [magento/magento2#17163](https://github.com/magento/magento2/pull/17163) -- Add meta NOINDEX,NOFOLLOW to admin scope to avoid accidental crawling (by @cmtickle) + * [magento/magento2#17035](https://github.com/magento/magento2/pull/17035) -- Replaced deprecated methods. (by @tiagosampaio) + * [magento/magento2#17227](https://github.com/magento/magento2/pull/17227) -- Replaced deprecated methods. (by @tiagosampaio) + +2.2.5 +============= +* GitHub issues: + * [#7720](https://github.com/magento/magento2/issues/7720) -- Product Repository saves attribute values for existing product always on "Default Store Level" (fixed in [magento-engcom/magento2ce#967](https://github.com/magento-engcom/magento2ce/pull/967)) + * [#12186](https://github.com/magento/magento2/issues/12186) -- Custom attributes values not updated (fixed in [magento-engcom/magento2ce#967](https://github.com/magento-engcom/magento2ce/pull/967)) + * [#12395](https://github.com/magento/magento2/issues/12395) -- Custom Magento CLI command has incorrect current store id. (fixed in [magento-engcom/magento2ce#967](https://github.com/magento-engcom/magento2ce/pull/967)) + * [#12792](https://github.com/magento/magento2/issues/12792) -- [2.1.10] No order confirmation email after paying with PayPal Express (fixed in [magento/magento2#13898](https://github.com/magento/magento2/pull/13898)) + * [#13778](https://github.com/magento/magento2/issues/13778) -- Braintree Paypal Method No Order Confirmation Email Sent (fixed in [magento/magento2#13898](https://github.com/magento/magento2/pull/13898)) + * [#13556](https://github.com/magento/magento2/issues/13556) -- Sorting in Product Listing via Quantity not work (fixed in [magento/magento2#13691](https://github.com/magento/magento2/pull/13691)) + * [#13769](https://github.com/magento/magento2/issues/13769) -- Order Email Sender (fixed in [magento/magento2#13878](https://github.com/magento/magento2/pull/13878)) + * [#12405](https://github.com/magento/magento2/issues/12405) -- Magento 2.2.1 - Impossible to create a new storeview (fixed in [magento/magento2#13943](https://github.com/magento/magento2/pull/13943)) + * [#12421](https://github.com/magento/magento2/issues/12421) -- 'Requested store is not found' when trying to create a store view in the back end (fixed in [magento/magento2#13943](https://github.com/magento/magento2/pull/13943)) + * [#13804](https://github.com/magento/magento2/issues/13804) -- Invoice grid shows wrong subtotal for partial items invoice. It shows order's subtotal instead if invoiced item's subtotal (fixed in [magento/magento2#13855](https://github.com/magento/magento2/pull/13855)) + * [#7372](https://github.com/magento/magento2/issues/7372) -- Product images gets removed from "Images And Videos" after validation alert. (fixed in [magento-engcom/magento2ce#1140](https://github.com/magento-engcom/magento2ce/pull/1140)) + * [#13385](https://github.com/magento/magento2/issues/13385) -- SQL query is printed into browser in case of exception (fixed in [magento/magento2#13607](https://github.com/magento/magento2/pull/13607)) + * [#13117](https://github.com/magento/magento2/issues/13117) -- Swatch Attribute is not getting save while deleting a swatch row with empty admin scope text (fixed in [magento/magento2#13717](https://github.com/magento/magento2/pull/13717)) + * [#3483](https://github.com/magento/magento2/issues/3483) -- Default country selection issue while creating new customer from backend (fixed in [magento/magento2#13024](https://github.com/magento/magento2/pull/13024)) + * [#13231](https://github.com/magento/magento2/issues/13231) -- Default State or Province is not pre-selected in the Estimate Shipping and Tax (fixed in [magento-engcom/magento2ce#1258](https://github.com/magento-engcom/magento2ce/pull/1258)) + * [#10559](https://github.com/magento/magento2/issues/10559) -- Extending swatch functionality using javascript mixins does not work in Safari and MS Edge (fixed in [magento/magento2#12929](https://github.com/magento/magento2/pull/12929)) + * [#5463](https://github.com/magento/magento2/issues/5463) -- The ability to store passwords using different hashing algorithms is limited (fixed in [magento/magento2#13884](https://github.com/magento/magento2/pull/13884)) + * [#13988](https://github.com/magento/magento2/issues/13988) -- Mini search field looses focus after its JavaScript is initialized (fixed in [magento/magento2#13989](https://github.com/magento/magento2/pull/13989)) + * [#13820](https://github.com/magento/magento2/issues/13820) -- IE11 minicart not updating on configurable product page (ES6) (fixed in [magento/magento2#14105](https://github.com/magento/magento2/pull/14105)) + * [#14010](https://github.com/magento/magento2/issues/14010) -- Why Report Bugs link not open in new tab? (fixed in [magento/magento2#14121](https://github.com/magento/magento2/pull/14121)) + * [#12205](https://github.com/magento/magento2/issues/12205) -- Stock inventory reindex bug (fixed in [magento-engcom/magento2ce#1134](https://github.com/magento-engcom/magento2ce/pull/1134)) + * [#8168](https://github.com/magento/magento2/issues/8168) -- Configurable product on wishlist shows parent image instead variation image (fixed in [magento-engcom/magento2ce#1031](https://github.com/magento-engcom/magento2ce/pull/1031)) + * [#14138](https://github.com/magento/magento2/issues/14138) -- Outdated package after upgrade sjparkinson/static-review is abandoned (fixed in [magento/magento2#14091](https://github.com/magento/magento2/pull/14091)) + * [#14109](https://github.com/magento/magento2/issues/14109) -- `MAX_NUM_COOKIES` doesn't follow the open-closed principle (fixed in [magento/magento2#14128](https://github.com/magento/magento2/pull/14128)) + * [#13704](https://github.com/magento/magento2/issues/13704) -- Category\Collection::joinUrlRewrite should use the store set on the collection (fixed in [magento/magento2#13716](https://github.com/magento/magento2/pull/13716)) + * [#13992](https://github.com/magento/magento2/issues/13992) -- Incorrect phpdoc should be Shipment\Item not Invoice\Item (fixed in [magento/magento2#14303](https://github.com/magento/magento2/pull/14303)) + * [#14089](https://github.com/magento/magento2/issues/14089) -- Malaysian (Malaysia) missing from locale list (fixed in [magento/magento2#14306](https://github.com/magento/magento2/pull/14306)) + * [#7428](https://github.com/magento/magento2/issues/7428) -- Multiline fields in forms have no visible label (fixed in [magento/magento2#14317](https://github.com/magento/magento2/pull/14317)) + * [#10057](https://github.com/magento/magento2/issues/10057) -- Editing order with backordered items results in new order not correctly marking order items as backordered (fixed in [magento/magento2#14327](https://github.com/magento/magento2/pull/14327)) + * [#10700](https://github.com/magento/magento2/issues/10700) -- Magento 2 Admin panel show loading on each page (fixed in [magento/magento2#14361](https://github.com/magento/magento2/pull/14361)) + * [#11930](https://github.com/magento/magento2/issues/11930) -- setup:di:compile's generated cache files inaccessible by the web-server user (fixed in [magento/magento2#14361](https://github.com/magento/magento2/pull/14361)) + * [#14072](https://github.com/magento/magento2/issues/14072) -- Change zip code validation pattern for Japan (fixed in [magento/magento2#14299](https://github.com/magento/magento2/pull/14299)) + * [#7816](https://github.com/magento/magento2/issues/7816) -- Customer_account.xml file abused (fixed in [magento/magento2#14325](https://github.com/magento/magento2/pull/14325)) + * [#10650](https://github.com/magento/magento2/issues/10650) -- Cron starts when it's already running (fixed in [magento/magento2#12497](https://github.com/magento/magento2/pull/12497)) + * [#14307](https://github.com/magento/magento2/issues/14307) -- Possible to press the "Previous" button while in the first step of the installation (fixed in [magento/magento2#14309](https://github.com/magento/magento2/pull/14309)) + * [#14249](https://github.com/magento/magento2/issues/14249) -- Priduct page price is using the hardcoded digits in js (fixed in [magento/magento2#14350](https://github.com/magento/magento2/pull/14350)) + * [#13582](https://github.com/magento/magento2/issues/13582) -- Magento 2.1.11 minimum quantity validation message not showing (fixed in [magento/magento2#13942](https://github.com/magento/magento2/pull/13942)) + * [#8837](https://github.com/magento/magento2/issues/8837) -- Google Analytics code being placed in body instead of head (fixed in [magento/magento2#14293](https://github.com/magento/magento2/pull/14293)) + * [#13010](https://github.com/magento/magento2/issues/13010) -- Write a Review page works on multistore for products that are not assigned to that store (fixed in [magento/magento2#14360](https://github.com/magento/magento2/pull/14360)) + * [#14049](https://github.com/magento/magento2/issues/14049) -- Retrieve session information from another customer under /customer/section/load/sections=&update_section_id=true (fixed in [magento/magento2#14176](https://github.com/magento/magento2/pull/14176)) + * [#6879](https://github.com/magento/magento2/issues/6879) -- Unable to change country of manufacture default label value (fixed in [magento/magento2#14319](https://github.com/magento/magento2/pull/14319)) + * [#14572](https://github.com/magento/magento2/issues/14572) -- Specify the table when adding field to filter for the collection Eav/Model/ResourceModel/Entity/Attribute/Option/Collection.php (fixed in [magento/magento2#14599](https://github.com/magento/magento2/pull/14599)) + * [#9666](https://github.com/magento/magento2/issues/9666) -- Magento 2.1.6 - Invoice PDF doesn't support Thai (fixed in [magento/magento2#13016](https://github.com/magento/magento2/pull/13016)) + * [#12323](https://github.com/magento/magento2/issues/12323) -- Magento 2.1.3 - Invoice and shipment PDF doesn't support Arabic (fixed in [magento/magento2#13016](https://github.com/magento/magento2/pull/13016)) + * [#14035](https://github.com/magento/magento2/issues/14035) -- Magento REST API, wrong condition for product list category filter (fixed in [magento/magento2#14048](https://github.com/magento/magento2/pull/14048)) + * [#14465](https://github.com/magento/magento2/issues/14465) -- [Indexes] Product 'version_id' lost last 'auro_increment' value after MySQL restart. (fixed in [magento/magento2#14635](https://github.com/magento/magento2/pull/14635)) + * [#13652](https://github.com/magento/magento2/issues/13652) -- Issue in product title with special chars in mini cart (fixed in [magento/magento2#14681](https://github.com/magento/magento2/pull/14681)) +* GitHub pull requests: + * [magento/magento2#13898](https://github.com/magento/magento2/pull/13898) -- Send order email for Braintree Paypal orders (by @pmclain) + * [magento/magento2#13956](https://github.com/magento/magento2/pull/13956) -- Use event object in 'ajax:addToCart' trigger when adding a product to the cart (by @koenner01) + * [magento/magento2#13691](https://github.com/magento/magento2/pull/13691) -- Fix for Issue-13556 - Sorting in Product Listing via Quantity not work (by @nuzil) + * [magento/magento2#13878](https://github.com/magento/magento2/pull/13878) -- Issues 13769. Fix wrong info about sent email in order sender. (by @pawcioma) + * [magento/magento2#13943](https://github.com/magento/magento2/pull/13943) -- magento/magento2#12405: Impossible to create a new storeview (by @hostep) + * [magento/magento2#13173](https://github.com/magento/magento2/pull/13173) -- Performance: remove count() form the condition section of a loop (by @Coderimus) + * [magento/magento2#13855](https://github.com/magento/magento2/pull/13855) -- Invoice grid shows wrong subtotal for partial items invoice. It shows order's subtotal instead if invoiced item's subtotal (by @ankurvr) + * [magento/magento2#14011](https://github.com/magento/magento2/pull/14011) -- Added alias to block 'product.info.description' (by @chedaroo) + * [magento/magento2#14013](https://github.com/magento/magento2/pull/14013) -- Use `^1.4` for `composer/composer` (by @sandermangel) + * [magento/magento2#14026](https://github.com/magento/magento2/pull/14026) -- [FIX] Remove not used variable in template (by @Coderimus) + * [magento/magento2#14030](https://github.com/magento/magento2/pull/14030) -- [FIX] Remove not used and empty template (by @Coderimus) + * [magento/magento2#11376](https://github.com/magento/magento2/pull/11376) -- [Backport 2.2-develop] PHP Livecodetest testCodeStyle() method does not use whitelist files (by @adrian-martinez-interactiv4) + * [magento/magento2#13977](https://github.com/magento/magento2/pull/13977) -- Backport of PR-10748 for Magento 2.2: Always use https for Vimeo vide… (by @hostep) + * [magento/magento2#14028](https://github.com/magento/magento2/pull/14028) -- [FIX] small refactoring and removing not using variable from templates (by @Coderimus) + * [magento/magento2#13607](https://github.com/magento/magento2/pull/13607) -- SQL query is printed into browser in case of exception (by @shyamranpara) + * [magento/magento2#13717](https://github.com/magento/magento2/pull/13717) -- [Backport 2.2] Solve problem saving empty swatches in admin (by @enriquei4) + * [magento/magento2#13807](https://github.com/magento/magento2/pull/13807) -- [Backport 2.2] Add quoting for base path in DI compile command (by @simpleadm) + * [magento/magento2#13024](https://github.com/magento/magento2/pull/13024) -- resolved default country selection issue while creating new customer … (by @pradeep-wagento) + * [magento/magento2#14044](https://github.com/magento/magento2/pull/14044) -- Make scope parameters of methods to save/delete config optional (by @avstudnitz) + * [magento/magento2#12929](https://github.com/magento/magento2/pull/12929) -- Issues #10559 - Extend swatch using mixins (M2.2) (by @srenon) + * [magento/magento2#13884](https://github.com/magento/magento2/pull/13884) -- #5463 - Use specified hashing algo in \Magento\Framework\Encryption\Encryptor::getHash (by @k4emic) + * [magento/magento2#13894](https://github.com/magento/magento2/pull/13894) -- Fix cache issue for currencies with no symbol (by @evgk) + * [magento/magento2#13989](https://github.com/magento/magento2/pull/13989) -- Act better on existing input focus instead of removing it (by @krzksz) + * [magento/magento2#14029](https://github.com/magento/magento2/pull/14029) -- Fix $useCache for container child blocks (by @tdgroot) + * [magento/magento2#14042](https://github.com/magento/magento2/pull/14042) -- Improve array output format for etc.php and config.php (by @avstudnitz) + * [magento/magento2#14062](https://github.com/magento/magento2/pull/14062) -- Typo in SSL port number (by @jasperzeinstra) + * [magento/magento2#14083](https://github.com/magento/magento2/pull/14083) -- Fix product attribute ordering when more than 10 attributes. (by @RandeKnight) + * [magento/magento2#14105](https://github.com/magento/magento2/pull/14105) -- magento/magento2#13820: IE11 minicart not updating on configurable pr… (by @Frodigo) + * [magento/magento2#14121](https://github.com/magento/magento2/pull/14121) -- [Backport] Open link "Report an Issue" in a new tab (by @sidolov) + * [magento/magento2#14041](https://github.com/magento/magento2/pull/14041) -- Removed unnecessary protected member variables. (by @Yogeshks) + * [magento/magento2#14106](https://github.com/magento/magento2/pull/14106) -- [FIX] several fixes for sales and tax module: not used imports, variables and legacy code (by @Coderimus) + * [magento/magento2#14136](https://github.com/magento/magento2/pull/14136) -- Added mage/translate component to customers's ajax login (by @ccasciotti) + * [magento/magento2#14154](https://github.com/magento/magento2/pull/14154) -- catalog:images:resize CLI command fix (by @nfourteen) + * [magento/magento2#14189](https://github.com/magento/magento2/pull/14189) -- fix incorrect phpdoc return type (by @EliasZ) + * [magento/magento2#11707](https://github.com/magento/magento2/pull/11707) -- UPS Option to include TAX in rate (by @gwharton) + * [magento/magento2#14156](https://github.com/magento/magento2/pull/14156) -- Add website- and storeview-code in stores admin grid (by @aschrammel) + * [magento/magento2#12893](https://github.com/magento/magento2/pull/12893) -- Improvement: Magento\Sales\Helper\Guest refactoring and bugfix (by @Coderimus) + * [magento/magento2#13653](https://github.com/magento/magento2/pull/13653) -- Update Store getConfig() to respect valid false return value (by @JeroenVanLeusden) + * [magento/magento2#14091](https://github.com/magento/magento2/pull/14091) -- Remove sjparkinson/static-review and other obsolete tools (by @orlangur) + * [magento/magento2#14128](https://github.com/magento/magento2/pull/14128) -- ISSUE-14109: Allow modification of cookies via extension (by @brideo) + * [magento/magento2#13716](https://github.com/magento/magento2/pull/13716) -- Category\Collection::joinUrlRewrite should use the store set on the collection (by @alepane21) + * [magento/magento2#14230](https://github.com/magento/magento2/pull/14230) -- Fix for broken navigation menu on IE11 (by @cstergianos) + * [magento/magento2#14306](https://github.com/magento/magento2/pull/14306) -- [#14089] Add Malaysian Locale Code (by @osrecio) + * [magento/magento2#14303](https://github.com/magento/magento2/pull/14303) -- Resolves PHPdoc issue in ticket #13992 (by @cream-julian) + * [magento/magento2#14317](https://github.com/magento/magento2/pull/14317) -- FR#7428 - Multiline fields in forms have no visible label (by @crisdiaz) + * [magento/magento2#14358](https://github.com/magento/magento2/pull/14358) -- Format code (by @mageprince) + * [magento/magento2#13414](https://github.com/magento/magento2/pull/13414) -- Add getters to product image builder (by @VincentMarmiesse) + * [magento/magento2#14308](https://github.com/magento/magento2/pull/14308) -- Added language translation, make proper sentence and removed unused delete html container (by @Yogeshks) + * [magento/magento2#14327](https://github.com/magento/magento2/pull/14327) -- magento/magento2#10057 (by @swnsma) + * [magento/magento2#14347](https://github.com/magento/magento2/pull/14347) -- [Backport 2.2] Add json and xml support to the post method in socket client (by @simpleadm) + * [magento/magento2#14361](https://github.com/magento/magento2/pull/14361) -- Removed cache backend option which explicitly set file permissions (by @xtremeperf) + * [magento/magento2#14388](https://github.com/magento/magento2/pull/14388) -- [FIX] Remove duplicated case statement (by @Coderimus) + * [magento/magento2#14060](https://github.com/magento/magento2/pull/14060) -- Disable add to cart button when redirect to cart enabled (by @ihor-sviziev) + * [magento/magento2#14299](https://github.com/magento/magento2/pull/14299) -- [#14072 2.2] Add Zip Pattern for Japan JP (by @osrecio) + * [magento/magento2#14325](https://github.com/magento/magento2/pull/14325) -- #7816: Customer_account.xml file abused (2.2) (by @mikewhitby) + * [magento/magento2#12497](https://github.com/magento/magento2/pull/12497) -- Prevent running again already running cron group (by @paveq) + * [magento/magento2#14288](https://github.com/magento/magento2/pull/14288) -- Fill visibility in AdminCreateConfigurableProductTest.xml (by @tdgroot) + * [magento/magento2#14385](https://github.com/magento/magento2/pull/14385) -- Remove improper unit test (by @orlangur) + * [magento/magento2#14309](https://github.com/magento/magento2/pull/14309) -- Disable "Back" button on the first step of the setup (by @ArjenMiedema) + * [magento/magento2#14350](https://github.com/magento/magento2/pull/14350) -- precision for price overriding by js (by @cdiacon) + * [magento/magento2#14403](https://github.com/magento/magento2/pull/14403) -- test command inside if/then clause broke before install script (by @edie-pasek) + * [magento/magento2#14440](https://github.com/magento/magento2/pull/14440) -- Removed extra backslash from comment block (by @Yogeshks) + * [magento/magento2#13942](https://github.com/magento/magento2/pull/13942) -- Issue #13582 show message for qty minAllowed, maxAllowed, qtyIncremen… (by @bordeo) + * [magento/magento2#14293](https://github.com/magento/magento2/pull/14293) -- Moved Google Analytics block code to head tag #8837 (by @KravetsAndriy) + * [magento/magento2#14439](https://github.com/magento/magento2/pull/14439) -- Update process-reviews.js (by @sanderjongsma) + * [magento/magento2#14445](https://github.com/magento/magento2/pull/14445) -- [FIX] Simplify ternary operators for catalog module (by @Coderimus) + * [magento/magento2#14455](https://github.com/magento/magento2/pull/14455) -- Fix button color on hover in email template (by @Karlasa) + * [magento/magento2#14452](https://github.com/magento/magento2/pull/14452) -- Remove else statements from \Magento\Framework\Session\SessionManager (by @adrian-martinez-interactiv4) + * [magento/magento2#14466](https://github.com/magento/magento2/pull/14466) -- Correct function return statement. (by @NamrataChangani) + * [magento/magento2#14473](https://github.com/magento/magento2/pull/14473) -- Added spanish Bolivia locale to allowedLocales list (by @JDavidVR) + * [magento/magento2#13808](https://github.com/magento/magento2/pull/13808) -- [Backport 2.2] Configurable product price options by store (by @simpleadm) + * [magento/magento2#14360](https://github.com/magento/magento2/pull/14360) -- Fix issue #13010. Check if product is assigned to current website. (by @afirlejczyk) + * [magento/magento2#14457](https://github.com/magento/magento2/pull/14457) -- [Backport 2.2] Return status in console commands (by @simpleadm) + * [magento/magento2#14498](https://github.com/magento/magento2/pull/14498) -- fix translation issue with rating stars (by @Karlasa) + * [magento/magento2#14504](https://github.com/magento/magento2/pull/14504) -- Check if store id is not null instead of empty (by @quisse) + * [magento/magento2#13629](https://github.com/magento/magento2/pull/13629) -- Fix translate issue (by @Corefix) + * [magento/magento2#13831](https://github.com/magento/magento2/pull/13831) -- Fixed comparison with 0 bug for TableRate shipping carrier (by @irs) + * [magento/magento2#14176](https://github.com/magento/magento2/pull/14176) -- Replace the existing headers with the no cache headers (by @joost-florijn-kega) + * [magento/magento2#14319](https://github.com/magento/magento2/pull/14319) -- 6879 - Unable to change country of manufacture default label value (by @MateuszChrapek) + * [magento/magento2#13257](https://github.com/magento/magento2/pull/13257) -- [FIX]: Recent orders are not filtered per store at the customer account page (by @Coderimus) + * [magento/magento2#14559](https://github.com/magento/magento2/pull/14559) -- Fix for Issue #13950 - Cache issue with configurable products related to currency-conversions (by @nuzil) + * [magento/magento2#14552](https://github.com/magento/magento2/pull/14552) -- Allow to configure min and max dates for date picker component (by @tkotosz) + * [magento/magento2#14599](https://github.com/magento/magento2/pull/14599) -- Specify the table when adding field to filter (by @PierreLeMaguer) + * [magento/magento2#13016](https://github.com/magento/magento2/pull/13016) -- Fix for sales PDFs to support more characters (by @rossmc) + * [magento/magento2#14048](https://github.com/magento/magento2/pull/14048) -- Fix for GitHub issue #14035. (by @kamilszewczyk) + * [magento/magento2#14629](https://github.com/magento/magento2/pull/14629) -- Refactor Code for Mass Order Unhold (by @AnshuMishra17) + * [magento/magento2#14635](https://github.com/magento/magento2/pull/14635) -- [Forwardport] magento/magento2#14465 Fix empty changelog tables after MySQL restart. (by @ihor-sviziev) + * [magento/magento2#14668](https://github.com/magento/magento2/pull/14668) -- Added hyphenation, cutting edge to cutting-edge. (by @surya07081995) + * [magento/magento2#14678](https://github.com/magento/magento2/pull/14678) -- Checkout page - Fix tooltip position on mobile devices (by @ihor-sviziev) + * [magento/magento2#14681](https://github.com/magento/magento2/pull/14681) -- [Backport] Fix #13652. Mini cart - fix issue in product title with special chars. (by @ihor-sviziev) + * [magento/magento2#14688](https://github.com/magento/magento2/pull/14688) -- Translate Action Label (by @net32) + * [magento/magento2#14696](https://github.com/magento/magento2/pull/14696) -- [Backport] Eliminate usage of "else" statements (by @ihor-sviziev) + +2.2.4 +============= +* GitHub issues: + * [#7691](https://github.com/magento/magento2/issues/7691) -- address with saveInAddressBook 0 are still being added to the address book for new customers (fixed in [magento/magento2#12171](https://github.com/magento/magento2/pull/12171)) + * [#9277](https://github.com/magento/magento2/issues/9277) -- Create new CLI command: enable/disable Magento Profiler (fixed in [magento/magento2#11407](https://github.com/magento/magento2/pull/11407)) + * [#11941](https://github.com/magento/magento2/issues/11941) -- Invoice for products that use qty decimal rounds down to whole number (fixed in [magento/magento2#11997](https://github.com/magento/magento2/pull/11997)) + * [#12083](https://github.com/magento/magento2/issues/12083) -- Cannot import zero (0) value into custom attribute (fixed in [magento/magento2#12283](https://github.com/magento/magento2/pull/12283)) + * [#3596](https://github.com/magento/magento2/issues/3596) -- Notice: Undefined index: value in /app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Select.php on line 72 (fixed in [magento/magento2#12296](https://github.com/magento/magento2/pull/12296)) + * [#9764](https://github.com/magento/magento2/issues/9764) -- exception message is wrong and misleading in findAccessorMethodName() of Magento\Framework\Reflection\NameFinder (fixed in [magento/magento2#12303](https://github.com/magento/magento2/pull/12303)) + * [#13214](https://github.com/magento/magento2/issues/13214) -- Not a correct displaying for Robots.txt (fixed in [magento/magento2#12310](https://github.com/magento/magento2/pull/12310)) + * [#9684](https://github.com/magento/magento2/issues/9684) -- No ACL set for integrations (fixed in [magento/magento2#12332](https://github.com/magento/magento2/pull/12332)) + * [#10438](https://github.com/magento/magento2/issues/10438) -- Potential error on order edit page when address has extension attributes (fixed in [magento/magento2#11787](https://github.com/magento/magento2/pull/11787)) + * [#11691](https://github.com/magento/magento2/issues/11691) -- Wrong return type for getAttributeText($attributeCode) (fixed in [magento/magento2#12003](https://github.com/magento/magento2/pull/12003)) + * [#12261](https://github.com/magento/magento2/issues/12261) -- Order confirmation email contains non functioning links (fixed in [magento/magento2#12308](https://github.com/magento/magento2/pull/12308)) + * [#12146](https://github.com/magento/magento2/issues/12146) -- Customer with empty "Date of Birth" cannot be saved (fixed in [magento/magento2#12302](https://github.com/magento/magento2/pull/12302)) + * [#10502](https://github.com/magento/magento2/issues/10502) -- Fatal error: Call getTranslateInline of null when generating some sitemap with errors (fixed in [magento/magento2#11320](https://github.com/magento/magento2/pull/11320)) + * [#11139](https://github.com/magento/magento2/issues/11139) -- Product Repeat Isuue after filter on category listing page. (fixed in [magento/magento2#11429](https://github.com/magento/magento2/pull/11429)) + * [#8003](https://github.com/magento/magento2/issues/8003) -- Using System Value for Base Currency Results in Config Error (fixed in [magento/magento2#11809](https://github.com/magento/magento2/pull/11809)) + * [#10347](https://github.com/magento/magento2/issues/10347) -- Wrong order tax amounts displayed when using specific tax configuration (fixed in [magento/magento2#11592](https://github.com/magento/magento2/pull/11592)) + * [#9360](https://github.com/magento/magento2/issues/9360) -- field doesn't work in system.xml for "radios" fields (fixed in [magento/magento2#11539](https://github.com/magento/magento2/pull/11539)) + * [#11792](https://github.com/magento/magento2/issues/11792) -- Can't add customizable options to product (fixed in [magento/magento2#11965](https://github.com/magento/magento2/pull/11965)) + * [#11528](https://github.com/magento/magento2/issues/11528) -- Validation prevents form closing (fixed in [magento/magento2#12048](https://github.com/magento/magento2/pull/12048)) + * [#12064](https://github.com/magento/magento2/issues/12064) -- Database Rollback not working with magento 2.1.9? (fixed in [magento/magento2#12108](https://github.com/magento/magento2/pull/12108)) + * [#9413](https://github.com/magento/magento2/issues/9413) -- Cannot remove product_list_toolbar in XML (fixed in [magento/magento2#11473](https://github.com/magento/magento2/pull/11473)) + * [#11669](https://github.com/magento/magento2/issues/11669) -- API salesRefundInvoiceV1 does no save invoice ID on credit memo (fixed in [magento/magento2#11670](https://github.com/magento/magento2/pull/11670)) + * [#11740](https://github.com/magento/magento2/issues/11740) -- Sending emails from Admin in Multi-Store Environment defaults to Primary Store (fixed in [magento/magento2#11992](https://github.com/magento/magento2/pull/11992)) + * [#9410](https://github.com/magento/magento2/issues/9410) -- Impossible to add swatch options via Service Contracts if there is no existing swatch option for attribute (fixed in [magento/magento2#12036](https://github.com/magento/magento2/pull/12036)) + * [#10707](https://github.com/magento/magento2/issues/10707) -- Create attribute option via API for swatch attribute fails (fixed in [magento/magento2#12036](https://github.com/magento/magento2/pull/12036)) + * [#10737](https://github.com/magento/magento2/issues/10737) -- Can't import attribute option over API if option is 'visual swatch' (fixed in [magento/magento2#12036](https://github.com/magento/magento2/pull/12036)) + * [#11032](https://github.com/magento/magento2/issues/11032) -- Unable to add new options to swatch attribute (fixed in [magento/magento2#12036](https://github.com/magento/magento2/pull/12036)) + * [#10128](https://github.com/magento/magento2/issues/10128) -- New Orders not being saved to order grid (fixed in [magento/magento2#12241](https://github.com/magento/magento2/pull/12241)) + * [#9515](https://github.com/magento/magento2/issues/9515) -- South Korea Zip Code Validation incorrect (fixed in [magento-engcom/magento2ce#903](https://github.com/magento-engcom/magento2ce/pull/903)) + * [#10210](https://github.com/magento/magento2/issues/10210) -- Transport variable can not be altered in email_invoice_set_template_vars_before Event (fixed in [magento/magento2#12132](https://github.com/magento/magento2/pull/12132)) + * [#11341](https://github.com/magento/magento2/issues/11341) -- Attribute category_ids issue (fixed in [magento/magento2#11389](https://github.com/magento/magento2/pull/11389)) + * [#12127](https://github.com/magento/magento2/issues/12127) -- Apostrophe in attribute option value in admin is not handled properly (fixed in [magento/magento2#12133](https://github.com/magento/magento2/pull/12133)) + * [#12058](https://github.com/magento/magento2/issues/12058) -- Can't save emoji in custom product options (fixed in [magento/magento2#12253](https://github.com/magento/magento2/pull/12253)) + * [#9742](https://github.com/magento/magento2/issues/9742) -- Default welcome message returns after being deleted (fixed in [magento/magento2#12328](https://github.com/magento/magento2/pull/12328)) + * [#9468](https://github.com/magento/magento2/issues/9468) -- REST API bundle-products/:sku/options/all always return is not authorized (fixed in [magento-engcom/magento2ce#904](https://github.com/magento-engcom/magento2ce/pull/904)) + * [#6634](https://github.com/magento/magento2/issues/6634) -- Yes/No attribute value is not shown on a product details page (fixed in [magento/magento2#12057](https://github.com/magento/magento2/pull/12057)) + * [#9961](https://github.com/magento/magento2/issues/9961) -- Unused product attributes display with value N/A or NO on storefront (fixed in [magento/magento2#12057](https://github.com/magento/magento2/pull/12057)) + * [#9931](https://github.com/magento/magento2/issues/9931) -- Empty image alt-text & missing alt attribute on product detail page (fixed in [magento/magento2#11323](https://github.com/magento/magento2/pull/11323)) + * [#11236](https://github.com/magento/magento2/issues/11236) -- Web Setup Wizard Icon Inconsistency (fixed in [magento/magento2#11388](https://github.com/magento/magento2/pull/11388)) + * [#11484](https://github.com/magento/magento2/issues/11484) -- Visual Merchandiser show prices of out of stock simple products for the associated configurable product. (fixed in [magento/magento2#11485](https://github.com/magento/magento2/pull/11485)) + * [#8255](https://github.com/magento/magento2/issues/8255) -- Export Products action doesn't consider hide_for_product_page value (fixed in [magento/magento2#11926](https://github.com/magento/magento2/pull/11926)) + * [#11509](https://github.com/magento/magento2/issues/11509) -- Psr logger debug method does not work by the default in developer mode (fixed in [magento/magento2#12207](https://github.com/magento/magento2/pull/12207)) + * [#11882](https://github.com/magento/magento2/issues/11882) -- It's not possible to enable "log to file" (debugging) in production mode (fixed in [magento/magento2#12207](https://github.com/magento/magento2/pull/12207)) + * [#9918](https://github.com/magento/magento2/issues/9918) -- Magento 2 automatically disables maintenance mode after certain actions (fixed in [magento/magento2#11052](https://github.com/magento/magento2/pull/11052)) + * [#11825](https://github.com/magento/magento2/issues/11825) -- 2.1.9 Item not added to the Wishlist if the user is not logged at the moment he click on the button to add it. (fixed in [magento/magento2#12038](https://github.com/magento/magento2/pull/12038)) + * [#11908](https://github.com/magento/magento2/issues/11908) -- Adding to wishlist doesn't work when not logged in (fixed in [magento/magento2#12038](https://github.com/magento/magento2/pull/12038)) + * [#758](https://github.com/magento/magento2/issues/758) -- Coding standards: arrays (fixed in [magento/magento2#12499](https://github.com/magento/magento2/pull/12499)) + * [#11324](https://github.com/magento/magento2/issues/11324) -- Updating a product via the REST API assigns it to all websites automatically. (fixed in [magento/magento2#11444](https://github.com/magento/magento2/pull/11444)) + * [#9633](https://github.com/magento/magento2/issues/9633) -- Web Setup Wizard 500 error when session storage is configured to use memcache (fixed in [magento/magento2#11608](https://github.com/magento/magento2/pull/11608)) + * [#6770](https://github.com/magento/magento2/issues/6770) -- M2.1.1 : Re-saving a product attribute with a different name than it's code results in an error (fixed in [magento/magento2#11617](https://github.com/magento/magento2/pull/11617)) + * [#11059](https://github.com/magento/magento2/issues/11059) -- 92 usages of expectException() with ignored $message parameter (fixed in [magento/magento2#11099](https://github.com/magento/magento2/pull/11099)) + * [#11409](https://github.com/magento/magento2/issues/11409) -- Too many password reset requests even when disabled in settings (fixed in [magento/magento2#11435](https://github.com/magento/magento2/pull/11435)) + * [#12110](https://github.com/magento/magento2/issues/12110) -- Missing cascade into attribute set deletion (fixed in [magento/magento2#12167](https://github.com/magento/magento2/pull/12167)) + * [#12268](https://github.com/magento/magento2/issues/12268) -- Gallery issues on configurable product page (fixed in [magento/magento2#12469](https://github.com/magento/magento2/pull/12469) and [magento-engcom/magento2ce#991](https://github.com/magento-engcom/magento2ce/pull/991)) + * [#12506](https://github.com/magento/magento2/issues/12506) -- Fixup typo getDispretionPath -> getDispersionPath (fixed in [magento/magento2#12507](https://github.com/magento/magento2/pull/12507)) + * [#12482](https://github.com/magento/magento2/issues/12482) -- Sitemap image links in MultiStore (fixed in [magento-engcom/magento2ce#935](https://github.com/magento-engcom/magento2ce/pull/935)) + * [#8437](https://github.com/magento/magento2/issues/8437) -- Silent error when an email template is not found (fixed in [magento-engcom/magento2ce#970](https://github.com/magento-engcom/magento2ce/pull/970)) + * [#8176](https://github.com/magento/magento2/issues/8176) -- LinkManagement::getChildren() does not include product ID's (and visibility) (fixed in [magento-engcom/magento2ce#986](https://github.com/magento-engcom/magento2ce/pull/986)) + * [#12613](https://github.com/magento/magento2/issues/12613) -- Verbiage Update Required: Product Image Watermark size Validation Message (fixed in [magento-engcom/magento2ce#985](https://github.com/magento-engcom/magento2ce/pull/985)) + * [#12180](https://github.com/magento/magento2/issues/12180) -- M2.2.1 Unable to open Address book after account creation (fixed in [magento/magento2#12220](https://github.com/magento/magento2/pull/12220)) + * [#12450](https://github.com/magento/magento2/issues/12450) -- Store not found when adding a ? to site URL. (fixed in [magento/magento2#12529](https://github.com/magento/magento2/pull/12529)) + * [#12468](https://github.com/magento/magento2/issues/12468) -- Sort by Price not working on CatalogSearch Page in Magento 2 (fixed in [magento-engcom/magento2ce#929](https://github.com/magento-engcom/magento2ce/pull/929)) + * [#7467](https://github.com/magento/magento2/issues/7467) -- File Put Contents file with empty content (fixed in [magento-engcom/magento2ce#962](https://github.com/magento-engcom/magento2ce/pull/962)) + * [#8410](https://github.com/magento/magento2/issues/8410) -- Custom Checkout Step and Shipping Step are Highlighted and Combined upon Checkout page load (fixed in [magento-engcom/magento2ce#975](https://github.com/magento-engcom/magento2ce/pull/975)) + * [#12582](https://github.com/magento/magento2/issues/12582) -- Can't remove item description from wishlist (fixed in [magento-engcom/magento2ce#981](https://github.com/magento-engcom/magento2ce/pull/981)) + * [#8862](https://github.com/magento/magento2/issues/8862) -- Can't emptying values by magento 2 api (fixed in [magento-engcom/magento2ce#916](https://github.com/magento-engcom/magento2ce/pull/916)) + * [#8011](https://github.com/magento/magento2/issues/8011) -- Strip Tags from attribute (fixed in [magento-engcom/magento2ce#968](https://github.com/magento-engcom/magento2ce/pull/968)) + * [#12526](https://github.com/magento/magento2/issues/12526) -- Currency change, Bank Transfer but checkout page shows "Your credit card will be charged for" (fixed in [magento-engcom/magento2ce#993](https://github.com/magento-engcom/magento2ce/pull/993)) + * [#12535](https://github.com/magento/magento2/issues/12535) -- Product change sku via repository (fixed in [magento-engcom/magento2ce#984](https://github.com/magento-engcom/magento2ce/pull/984)) + * [#8507](https://github.com/magento/magento2/issues/8507) -- There is invalid type in PHPDoc block of \Magento\Framework\Data\Tree::getNodeById() (fixed in [magento-engcom/magento2ce#964](https://github.com/magento-engcom/magento2ce/pull/964)) + * [#10123](https://github.com/magento/magento2/issues/10123) -- Invoice entity_model in table eav_entity_type (fixed in [magento-engcom/magento2ce#980](https://github.com/magento-engcom/magento2ce/pull/980)) + * [#9055](https://github.com/magento/magento2/issues/9055) -- Default Store is always used when retrieving sequence value's for sales entity's. (fixed in [magento/magento2#11702](https://github.com/magento/magento2/pull/11702)) + * [#8601](https://github.com/magento/magento2/issues/8601) -- Can bypass Minimum Order Amount Logic (fixed in [magento-engcom/magento2ce#963](https://github.com/magento-engcom/magento2ce/pull/963)) + * [#10797](https://github.com/magento/magento2/issues/10797) -- catalogProductTierPriceManagementV1 DELETE and POST operation wipes out media gallery selections when used on store code "all". (fixed in [magento-engcom/magento2ce#977](https://github.com/magento-engcom/magento2ce/pull/977)) + * [#12560](https://github.com/magento/magento2/issues/12560) -- Back-End issue for multi-store website: when editing Order shipping/billing address - allowed countries are selected from wrong Store View (fixed in [magento-engcom/magento2ce#982](https://github.com/magento-engcom/magento2ce/pull/982)) + * [#2907](https://github.com/magento/magento2/issues/2907) -- Integration Test Annotation magentoAppArea breaks with some valid values (fixed in [magento-engcom/magento2ce#996](https://github.com/magento-engcom/magento2ce/pull/996)) + * [#5738](https://github.com/magento/magento2/issues/5738) -- SearchCriteriaBuilder builds wrong criteria (ORDER BY part) (fixed in [magento-engcom/magento2ce#1003](https://github.com/magento-engcom/magento2ce/pull/1003)) + * [#12259](https://github.com/magento/magento2/issues/12259) -- Save and Duplicated product not working (fixed in [magento-engcom/magento2ce#983](https://github.com/magento-engcom/magento2ce/pull/983)) + * [#8204](https://github.com/magento/magento2/issues/8204) -- catalog:images:resize = getimagesize(): Read error! in vendor/magento/module-catalog/Model/Product/Image.php on line 410 if an image is 0 bytes (fixed in [magento-engcom/magento2ce#1000](https://github.com/magento-engcom/magento2ce/pull/1000)) + * [#12285](https://github.com/magento/magento2/issues/12285) -- The option false for mobile device don't work in product view page gallery (fixed in [magento-engcom/magento2ce#1006](https://github.com/magento-engcom/magento2ce/pull/1006)) + * [#12490](https://github.com/magento/magento2/issues/12490) -- I can't disable full screen gallery on mobile on magento 2.2.1 (fixed in [magento-engcom/magento2ce#1006](https://github.com/magento-engcom/magento2ce/pull/1006)) + * [#10814](https://github.com/magento/magento2/issues/10814) -- Attribute repository resets sourceModel for new attributes (fixed in [magento-engcom/magento2ce#1012](https://github.com/magento-engcom/magento2ce/pull/1012)) + * [#12632](https://github.com/magento/magento2/issues/12632) -- Magento Connect no longer exist (fixed in [magento/magento2#12633](https://github.com/magento/magento2/pull/12633)) + * [#8647](https://github.com/magento/magento2/issues/8647) -- Order of how arguments are merged in multiple di.xml-files causes unexpected results (fixed in [magento-engcom/magento2ce#995](https://github.com/magento-engcom/magento2ce/pull/995)) + * [#12378](https://github.com/magento/magento2/issues/12378) -- Regions list in Directory module for India (fixed in [magento-engcom/magento2ce#1007](https://github.com/magento-engcom/magento2ce/pull/1007)) + * [#11946](https://github.com/magento/magento2/issues/11946) -- Layer navigation showing wrong product count (fixed in [magento/magento2#12063](https://github.com/magento/magento2/pull/12063)) + * [#12452](https://github.com/magento/magento2/issues/12452) -- ACL permissions issue (fixed in [magento/magento2#12661](https://github.com/magento/magento2/pull/12661)) + * [#12660](https://github.com/magento/magento2/issues/12660) -- Invalid parameter configuration provided for $block argument upon no ACL permissions to the block (fixed in [magento/magento2#12661](https://github.com/magento/magento2/pull/12661)) + * [#12084](https://github.com/magento/magento2/issues/12084) -- Product csv import > fail on round brackets in image filename (fixed in [magento-engcom/magento2ce#1017](https://github.com/magento-engcom/magento2ce/pull/1017)) + * [#12656](https://github.com/magento/magento2/issues/12656) -- Checkout: Whitespace in front of coupon code causes "Coupon code is not valid" (fixed in [magento-engcom/magento2ce#1021](https://github.com/magento-engcom/magento2ce/pull/1021)) + * [#12667](https://github.com/magento/magento2/issues/12667) -- Incorrect partial attribute (EAV) reindex (Update by Schedule) for configurable product with childs visibility "Not Visible Individually" (fixed in [magento-engcom/magento2ce#1023](https://github.com/magento-engcom/magento2ce/pull/1023)) + * [#10743](https://github.com/magento/magento2/issues/10743) -- Magento 2 is not showing Popular Search Terms (fixed in [magento-engcom/magento2ce#1024](https://github.com/magento-engcom/magento2ce/pull/1024)) + * [#5774](https://github.com/magento/magento2/issues/5774) -- Tier price and custom options give bad results (fixed in [magento/magento2#11563](https://github.com/magento/magento2/pull/11563)) + * [#8615](https://github.com/magento/magento2/issues/8615) -- REST API unable to make requests with slash (/) in SKU (fixed in [magento-engcom/magento2ce#949](https://github.com/magento-engcom/magento2ce/pull/949)) + * [#10133](https://github.com/magento/magento2/issues/10133) -- Please add your expectations for @deprecated annotations (fixed in [magento/magento2#11070](https://github.com/magento/magento2/pull/11070)) + * [#12713](https://github.com/magento/magento2/issues/12713) -- Currency symbol overlaps entered attribute option's price while creating Configurable Product (fixed in [magento/magento2#12730](https://github.com/magento/magento2/pull/12730)) + * [#9453](https://github.com/magento/magento2/issues/9453) -- Reopened: '?SID' in URL even if disabled (fixed in [magento/magento2#12743](https://github.com/magento/magento2/pull/12743)) + * [#9720](https://github.com/magento/magento2/issues/9720) -- Menu item dependencies (dependsOnModule, dependsOnConfig) are broken (fixed in [magento/magento2#12747](https://github.com/magento/magento2/pull/12747)) + * [#6965](https://github.com/magento/magento2/issues/6965) -- \Magento\Directory\Model\PriceCurrency::format() fails without conversion rate (fixed in [magento-engcom/magento2ce#1022](https://github.com/magento-engcom/magento2ce/pull/1022)) + * [#12627](https://github.com/magento/magento2/issues/12627) -- Referer is not added to login url in checkout config (fixed in [magento/magento2#12630](https://github.com/magento/magento2/pull/12630)) + * [#12206](https://github.com/magento/magento2/issues/12206) -- Tracking link returns 404 page in admin panel (fixed in [magento/magento2#12732](https://github.com/magento/magento2/pull/12732)) + * [#6113](https://github.com/magento/magento2/issues/6113) -- Validate range-words in Form component (UI Component) (fixed in [magento/magento2#12739](https://github.com/magento/magento2/pull/12739)) + * [#12719](https://github.com/magento/magento2/issues/12719) -- Welcome message is shown with customer's first and last names after confirming account (fixed in [magento/magento2#12738](https://github.com/magento/magento2/pull/12738)) + * [#5035](https://github.com/magento/magento2/issues/5035) -- I can not to subscribe on change of all sections in Stores ->Configuration using event admin_system_config_changed_section (fixed in [magento/magento2#12758](https://github.com/magento/magento2/pull/12758)) + * [#12715](https://github.com/magento/magento2/issues/12715) -- Storefront Back to Sign in button does not work as expected (fixed in [magento/magento2#12759](https://github.com/magento/magento2/pull/12759)) + * [#11743](https://github.com/magento/magento2/issues/11743) -- AbstractPdf - ZendException font is not set (fixed in [magento-engcom/magento2ce#1016](https://github.com/magento-engcom/magento2ce/pull/1016)) + * [#7241](https://github.com/magento/magento2/issues/7241) -- No option to start with blank option for prefix and suffix in checkout. (fixed in [magento/magento2#11462](https://github.com/magento/magento2/pull/11462)) + * [#5188](https://github.com/magento/magento2/issues/5188) -- Error generating URN-catalog when blank one exists (fixed in [magento/magento2#11686](https://github.com/magento/magento2/pull/11686)) + * [#11936](https://github.com/magento/magento2/issues/11936) -- required attribute set id filter on attribute group repository getList (fixed in [magento/magento2#12105](https://github.com/magento/magento2/pull/12105)) + * [#12625](https://github.com/magento/magento2/issues/12625) -- when saving a page in magento 2.2.1, 'Modified' date field is not getting updated (fixed in [magento/magento2#12636](https://github.com/magento/magento2/pull/12636)) + * [#11953](https://github.com/magento/magento2/issues/11953) -- Product configuration creator does not warn about invalid SKUs (fixed in [magento/magento2#12737](https://github.com/magento/magento2/pull/12737)) + * [#12439](https://github.com/magento/magento2/issues/12439) -- Newsletter subscription success email not sent after confirmation (fixed in [magento/magento2#12751](https://github.com/magento/magento2/pull/12751)) + * [#8830](https://github.com/magento/magento2/issues/8830) -- Can`t delete row in dynamicRows component (fixed in [magento-engcom/magento2ce#921](https://github.com/magento-engcom/magento2ce/pull/921)) + * [#12712](https://github.com/magento/magento2/issues/12712) -- Latest Google Chrome Browser issue with duplicate #email (fixed in [magento-engcom/magento2ce#1036](https://github.com/magento-engcom/magento2ce/pull/1036)) + * [#6916](https://github.com/magento/magento2/issues/6916) -- Update Bundle Product without changes in bundle items (fixed in [magento/magento2#12734](https://github.com/magento/magento2/pull/12734)) + * [#12374](https://github.com/magento/magento2/issues/12374) -- Model hasDataChanges always true (fixed in [magento/magento2#12736](https://github.com/magento/magento2/pull/12736)) + * [#11885](https://github.com/magento/magento2/issues/11885) -- Magento 2.2 Paypal Can't Accept Checkout Agreements Before Routing to PayPal (fixed in [magento/magento2#12401](https://github.com/magento/magento2/pull/12401)) + * [#12844](https://github.com/magento/magento2/issues/12844) -- "Cannot instantiate interface Magento\Framework\Interception\ObjectManager\ConfigInterface" error in integration tests (fixed in [magento/magento2#12845](https://github.com/magento/magento2/pull/12845)) + * [#12294](https://github.com/magento/magento2/issues/12294) -- Bug: Adding Custom Attribute - The value of Admin scope can't be empty (fixed in [magento/magento2#12755](https://github.com/magento/magento2/pull/12755)) + * [#12900](https://github.com/magento/magento2/issues/12900) -- Braintree "Place Order" button is disabled after failed validation (fixed in [magento/magento2#12902](https://github.com/magento/magento2/pull/12902)) + * [#12555](https://github.com/magento/magento2/issues/12555) -- Naming collision in Javascript ui registry (backend) (fixed in [magento/magento2#12945](https://github.com/magento/magento2/pull/12945)) + * [#4292](https://github.com/magento/magento2/issues/4292) -- Why can't one switch back to default mode ? (fixed in [magento/magento2#12752](https://github.com/magento/magento2/pull/12752)) + * [#2156](https://github.com/magento/magento2/issues/2156) -- Why does \Magento\Translation\Model\Js\DataProvider use \Magento\Framework\Phrase\Renderer\Translate, not \Magento\Framework\Phrase\Renderer\Composite? (fixed in [magento/magento2#12953](https://github.com/magento/magento2/pull/12953)) + * [#7441](https://github.com/magento/magento2/issues/7441) -- Configurable attribute options are not sorted (fixed in [magento/magento2#12963](https://github.com/magento/magento2/pull/12963)) + * [#10869](https://github.com/magento/magento2/issues/10869) -- field lengths differ across many tables (fixed in [magento/magento2#13015](https://github.com/magento/magento2/pull/13015)) + * [#12446](https://github.com/magento/magento2/issues/12446) -- Remove /home from the sitemap.xml (fixed in [magento/magento2#12649](https://github.com/magento/magento2/pull/12649)) + * [#12894](https://github.com/magento/magento2/issues/12894) -- Can't remove State is required for all countries (fixed in [magento/magento2#12917](https://github.com/magento/magento2/pull/12917)) + * [#12393](https://github.com/magento/magento2/issues/12393) -- Attribute with "Catalog Input Type for Store Owner" equal "Fixed Product Tax" for Multi-store (fixed in [magento/magento2#13019](https://github.com/magento/magento2/pull/13019)) + * [#9036](https://github.com/magento/magento2/issues/9036) -- Database backup doesn't include triggers (fixed in [magento/magento2#11369](https://github.com/magento/magento2/pull/11369)) + * [#12209](https://github.com/magento/magento2/issues/12209) -- Substitution payment method - Incorrect message (fixed in [magento/magento2#12731](https://github.com/magento/magento2/pull/12731)) + * [#10415](https://github.com/magento/magento2/issues/10415) -- Customer First and Last names not being trimmed of leading and trailing spaces on save. (fixed in [magento/magento2#12964](https://github.com/magento/magento2/pull/12964)) + * [#12601](https://github.com/magento/magento2/issues/12601) -- A space between the category page and the main footer when applying specific settings (fixed in [magento/magento2#13026](https://github.com/magento/magento2/pull/13026)) + * [#12320](https://github.com/magento/magento2/issues/12320) -- Newsletter subscribe button title wrapped (fixed in [magento/magento2#13041](https://github.com/magento/magento2/pull/13041) and [magento/magento2#13029](https://github.com/magento/magento2/pull/13029)) + * [#11796](https://github.com/magento/magento2/issues/11796) -- Magento2.2.0 home page product grid issues (fixed in [magento/magento2#13081](https://github.com/magento/magento2/pull/13081)) + * [#12828](https://github.com/magento/magento2/issues/12828) -- Uncaught Error: Script error for: trackingCode error on every frontend page (fixed in [magento/magento2#13061](https://github.com/magento/magento2/pull/13061)) + * [#5129](https://github.com/magento/magento2/issues/5129) -- Product details page zoom issue when dropdown menu have overlap area with it. (fixed in [magento/magento2#13084](https://github.com/magento/magento2/pull/13084)) + * [#6486](https://github.com/magento/magento2/issues/6486) -- Unable to save certain product properties via Rest API (fixed in [magento-engcom/magento2ce#1018](https://github.com/magento-engcom/magento2ce/pull/1018)) + * [#9969](https://github.com/magento/magento2/issues/9969) -- Cancel order and restore quote methods increase stocks twice (fixed in [magento/magento2#12668](https://github.com/magento/magento2/pull/12668)) + * [#12221](https://github.com/magento/magento2/issues/12221) -- Google analytics pageview being triggered twice (fixed in [magento/magento2#13034](https://github.com/magento/magento2/pull/13034)) + * [#12705](https://github.com/magento/magento2/issues/12705) -- Integrity constraint violation error after reordering product with custom options (fixed in [magento/magento2#13036](https://github.com/magento/magento2/pull/13036)) + * [#12876](https://github.com/magento/magento2/issues/12876) -- Multiple newsletter confirmation emails sent (fixed in [magento/magento2#13044](https://github.com/magento/magento2/pull/13044)) + * [#8114](https://github.com/magento/magento2/issues/8114) -- "Save Block"-button on Add New Block silently ignores clicks if the content is empty. (fixed in [magento-engcom/magento2ce#1032](https://github.com/magento-engcom/magento2ce/pull/1032)) + * [#8453](https://github.com/magento/magento2/issues/8453) -- Price outlining in Invoice PDF (fixed in [magento-engcom/magento2ce#1216](https://github.com/magento-engcom/magento2ce/pull/1216)) + * [#12967](https://github.com/magento/magento2/issues/12967) -- Undeclared dependency magento/zendframework1 by magento/framework (fixed in [magento/magento2#12990](https://github.com/magento/magento2/pull/12990)) + * [#12787](https://github.com/magento/magento2/issues/12787) -- Newsletter\Model\Subscriber::loadByEmail() does not use MySQL index (fixed in [magento/magento2#13033](https://github.com/magento/magento2/pull/13033)) + * [#12877](https://github.com/magento/magento2/issues/12877) -- [2.2.1] Magento Database Backup Command Fails (Fix included) (fixed in [magento/magento2#13066](https://github.com/magento/magento2/pull/13066)) + * [#5550](https://github.com/magento/magento2/issues/5550) -- Incorrect language on swatch error (fixed in [magento-engcom/magento2ce#1117](https://github.com/magento-engcom/magento2ce/pull/1117)) + * [#11828](https://github.com/magento/magento2/issues/11828) -- Visual Swatches not showing swatch color in admin (fixed in [magento/magento2#13101](https://github.com/magento/magento2/pull/13101)) + * [#13095](https://github.com/magento/magento2/issues/13095) -- No locale for Swedish (Finland) (fixed in [magento-engcom/magento2ce#1207](https://github.com/magento-engcom/magento2ce/pull/1207)) + * [#11428](https://github.com/magento/magento2/issues/11428) -- Cart Price Rule Label is not working (fixed in [magento/magento2#13141](https://github.com/magento/magento2/pull/13141)) + * [#11497](https://github.com/magento/magento2/issues/11497) -- Discount Rule does not show Default Rule Label (fixed in [magento/magento2#13141](https://github.com/magento/magento2/pull/13141)) + * [#12430](https://github.com/magento/magento2/issues/12430) -- While assigning prices to configurable products, prices aren's readable when using custom price symbol. (fixed in [magento/magento2#13025](https://github.com/magento/magento2/pull/13025)) + * [#12322](https://github.com/magento/magento2/issues/12322) -- Bug with CDATA in XML layout update (fixed in [magento-engcom/magento2ce#1163](https://github.com/magento-engcom/magento2ce/pull/1163)) + * [#12714](https://github.com/magento/magento2/issues/12714) -- Extra records are in exported CSV file for order (fixed in [magento/magento2#13208](https://github.com/magento/magento2/pull/13208)) + * [#8624](https://github.com/magento/magento2/issues/8624) -- Stock status not coming back after qty update (fixed in [magento-engcom/magento2ce#955](https://github.com/magento-engcom/magento2ce/pull/955)) + * [#11897](https://github.com/magento/magento2/issues/11897) -- Catalog product list widget not working with multiple sku (fixed in [magento-engcom/magento2ce#1050](https://github.com/magento-engcom/magento2ce/pull/1050)) + * [#12147](https://github.com/magento/magento2/issues/12147) -- The function "isUsingStaticUrlsAllowed" (configuration setting "cms/wysiwyg/use_static_urls_in_catalog") doesn't have any effect with the WYSIWYG editor image insertion (fixed in [magento-engcom/magento2ce#1215](https://github.com/magento-engcom/magento2ce/pull/1215)) + * [#12819](https://github.com/magento/magento2/issues/12819) -- CartTotalRepository cannot handle extension attributes in quote addresses in 2.2.2 (fixed in [magento-engcom/magento2ce#1186](https://github.com/magento-engcom/magento2ce/pull/1186)) + * [#12993](https://github.com/magento/magento2/issues/12993) -- Type error in Cart/Totals (fixed in [magento-engcom/magento2ce#1186](https://github.com/magento-engcom/magento2ce/pull/1186)) + * [#12342](https://github.com/magento/magento2/issues/12342) -- JSTestDriver removal (fixed in [magento/magento2#12406](https://github.com/magento/magento2/pull/12406)) + * [#13126](https://github.com/magento/magento2/issues/13126) -- 2.2.2 - Duplicating Bundle Product Removes Bundle Options From Original Product (fixed in [magento-engcom/magento2ce#1217](https://github.com/magento-engcom/magento2ce/pull/1217)) + * [#7768](https://github.com/magento/magento2/issues/7768) -- Adding 'is_saleable' attribute to sort of product collection causes exception and adding 'is_salable' has no effect (fixed in [magento-engcom/magento2ce#1045](https://github.com/magento-engcom/magento2ce/pull/1045)) + * [#12231](https://github.com/magento/magento2/issues/12231) -- New Cart Rule : Small styles issue because of styles-old.css (fixed in [magento-engcom/magento2ce#1146](https://github.com/magento-engcom/magento2ce/pull/1146)) + * [#5697](https://github.com/magento/magento2/issues/5697) -- [2.1.0] Misleading feedback when sending tracking information email (fixed in [magento-engcom/magento2ce#1245](https://github.com/magento-engcom/magento2ce/pull/1245)) + * [#7213](https://github.com/magento/magento2/issues/7213) -- WEBAPI: PHP session is always started 2.1.2 (fixed in [magento-engcom/magento2ce#1247](https://github.com/magento-engcom/magento2ce/pull/1247)) + * [#5948](https://github.com/magento/magento2/issues/5948) -- Magento 2 configurable product selection stock issue (fixed in [magento/magento2#12936](https://github.com/magento/magento2/pull/12936)) + * [#10661](https://github.com/magento/magento2/issues/10661) -- Opacity png watermark became white box on product images (fixed in [magento/magento2#11060](https://github.com/magento/magento2/pull/11060)) + * [#13327](https://github.com/magento/magento2/issues/13327) -- Menu ui-state-active not removed from previous opened menu item (fixed in [magento/magento2#13341](https://github.com/magento/magento2/pull/13341)) + * [#8621](https://github.com/magento/magento2/issues/8621) -- M2.1 Multishipping Checkout step New Address - Old State is saved when country is changed (fixed in [magento/magento2#13364](https://github.com/magento/magento2/pull/13364)) + * [#7760](https://github.com/magento/magento2/issues/7760) -- M2.1.2 : Shipment Tracking REST API should throw an error if order doesn't exist. (fixed in [magento-engcom/magento2ce#1162](https://github.com/magento-engcom/magento2ce/pull/1162)) + * [#7849](https://github.com/magento/magento2/issues/7849) -- M2.x.x Translation Missing in Checkout for Tax (fixed in [magento-engcom/magento2ce#1147](https://github.com/magento-engcom/magento2ce/pull/1147)) + * [#12860](https://github.com/magento/magento2/issues/12860) -- Sort by Product Name doesn't work with Ancor and available filters (fixed in [magento-engcom/magento2ce#1192](https://github.com/magento-engcom/magento2ce/pull/1192)) + * [#7848](https://github.com/magento/magento2/issues/7848) -- M2.1.x : Require Customer To Be Logged In To Checkout (fixed in [magento-engcom/magento2ce#1148](https://github.com/magento-engcom/magento2ce/pull/1148)) + * [#11527](https://github.com/magento/magento2/issues/11527) -- Notification messages not disappearing after being displayed (fixed in [magento-engcom/magento2ce#1111](https://github.com/magento-engcom/magento2ce/pull/1111)) + * [#7698](https://github.com/magento/magento2/issues/7698) -- Admin Global Search was build in a hurry (fixed in [magento-engcom/magento2ce#1167](https://github.com/magento-engcom/magento2ce/pull/1167)) + * [#12574](https://github.com/magento/magento2/issues/12574) -- ConfigurationTest fails when installing via composer (fixed in [magento-engcom/magento2ce#1161](https://github.com/magento-engcom/magento2ce/pull/1161)) + * [#11798](https://github.com/magento/magento2/issues/11798) -- Magento 2.1.9 - Refunding / Credit Memo Total Value is not updated (fixed in [magento-engcom/magento2ce#1185](https://github.com/magento-engcom/magento2ce/pull/1185)) + * [#13497](https://github.com/magento/magento2/issues/13497) -- Method getUrl in Magento\Catalog\Model\Product\Attribute\Frontend returns image url with double slash (fixed in [magento/magento2#13498](https://github.com/magento/magento2/pull/13498)) + * [#12081](https://github.com/magento/magento2/issues/12081) -- Magento 2.2.0: Translations for 'Item in Cart' missing in mini cart. (fixed in [magento/magento2#13528](https://github.com/magento/magento2/pull/13528)) + * [#11252](https://github.com/magento/magento2/issues/11252) -- Custom attribute - File not allowing uploads (fixed in [magento/magento2#13563](https://github.com/magento/magento2/pull/13563)) + * [#12817](https://github.com/magento/magento2/issues/12817) -- Coupon code with canceled order (fixed in [magento-engcom/magento2ce#1095](https://github.com/magento-engcom/magento2ce/pull/1095)) + * [#11963](https://github.com/magento/magento2/issues/11963) -- Magento 2.2 language switching not working on catalog and Product Pages (fixed in [magento-engcom/magento2ce#1143](https://github.com/magento-engcom/magento2ce/pull/1143)) + * [#12791](https://github.com/magento/magento2/issues/12791) -- Customer & Product Tax class wrongly styled (fixed in [magento/magento2#13643](https://github.com/magento/magento2/pull/13643)) + * [#13429](https://github.com/magento/magento2/issues/13429) -- Magento 2.2.2 password reset strength meter (fixed in [magento/magento2#13761](https://github.com/magento/magento2/pull/13761)) + * [#13760](https://github.com/magento/magento2/issues/13760) -- Remove deprecated Brazilian currencies in the setup process (fixed in [magento/magento2#13770](https://github.com/magento/magento2/pull/13770)) + * [#5451](https://github.com/magento/magento2/issues/5451) -- Rating titles with whitespace results in broken ID attributes (fixed in [magento-engcom/magento2ce#1119](https://github.com/magento-engcom/magento2ce/pull/1119)) + * [#8035](https://github.com/magento/magento2/issues/8035) -- Join extension attributes are not added to Order results (REST api) (fixed in [magento-engcom/magento2ce#1168](https://github.com/magento-engcom/magento2ce/pull/1168)) + * [#13595](https://github.com/magento/magento2/issues/13595) -- loadCache for Block Magento\Theme\Block\Html\Footer dont work (fixed in [magento/magento2#13762](https://github.com/magento/magento2/pull/13762)) + * [#10595](https://github.com/magento/magento2/issues/10595) -- Low Stock Report Grid Empty (fixed in [magento/magento2#13682](https://github.com/magento/magento2/pull/13682)) + * [#13315](https://github.com/magento/magento2/issues/13315) -- Mobile "Payment Methods" step looks bad on mobile (fixed in [magento/magento2#13777](https://github.com/magento/magento2/pull/13777)) + * [#13791](https://github.com/magento/magento2/issues/13791) -- Submitting search form (mini) with empty value throws error on preventDefault (fixed in [magento/magento2#13811](https://github.com/magento/magento2/pull/13811)) + * [#12711](https://github.com/magento/magento2/issues/12711) -- Default Welcome message is broken on storefront with enabled translate-inline (fixed in [magento/magento2#13038](https://github.com/magento/magento2/pull/13038)) + * [#5863](https://github.com/magento/magento2/issues/5863) -- URL Rewrite issues occur very often /catalog/product/view/id/711/s/product-name/category/16/ (fixed in [magento/magento2#13567](https://github.com/magento/magento2/pull/13567)) + * [#8227](https://github.com/magento/magento2/issues/8227) -- After upgrade to 2.1.3 url rewrite problem multi store (fixed in [magento/magento2#13567](https://github.com/magento/magento2/pull/13567)) + * [#8957](https://github.com/magento/magento2/issues/8957) -- Permanent Redirect for old URL missing via API (fixed in [magento/magento2#13567](https://github.com/magento/magento2/pull/13567)) + * [#10073](https://github.com/magento/magento2/issues/10073) -- Magento don't create product redirect if URL key on store view level was changed. (fixed in [magento/magento2#13567](https://github.com/magento/magento2/pull/13567)) + * [#13240](https://github.com/magento/magento2/issues/13240) -- Permanent 301 redirect is not generated when product url changes on storeview scope (fixed in [magento/magento2#13567](https://github.com/magento/magento2/pull/13567)) + * [#13768](https://github.com/magento/magento2/issues/13768) -- Expired backend password - Attention: Something went wrong (fixed in [magento/magento2#13787](https://github.com/magento/magento2/pull/13787)) + * [#4454](https://github.com/magento/magento2/issues/4454) -- CMS Page with in layout update xml (fixed in [magento/magento2#13817](https://github.com/magento/magento2/pull/13817)) + * [#13350](https://github.com/magento/magento2/issues/13350) -- Magento 2.2 Encoding Issue -> Google Analytics (fixed in [magento/magento2#13844](https://github.com/magento/magento2/pull/13844)) + * [#13827](https://github.com/magento/magento2/issues/13827) -- Google Analytics character encoding issue ( \u0020 ) (fixed in [magento/magento2#13844](https://github.com/magento/magento2/pull/13844)) + * [#7765](https://github.com/magento/magento2/issues/7765) -- Filter block on category is still present also mode is to just show "static block" (fixed in [magento-engcom/magento2ce#1159](https://github.com/magento-engcom/magento2ce/pull/1159)) + * [#11512](https://github.com/magento/magento2/issues/11512) -- Incorrect use of 503 status code (fixed in [magento/magento2#11513](https://github.com/magento/magento2/pull/11513)) + * [#12889](https://github.com/magento/magento2/issues/12889) -- Wrong shipping fee in backend with multiple store views (fixed in [magento-engcom/magento2ce#1132](https://github.com/magento-engcom/magento2ce/pull/1132)) + * [#13216](https://github.com/magento/magento2/issues/13216) -- `quoteAddressToFormAddressData` mutates the argument (fixed in [magento/magento2#13217](https://github.com/magento/magento2/pull/13217)) + * [#13631](https://github.com/magento/magento2/issues/13631) -- Totals sort order is not respected in customer account order view (fixed in [magento/magento2#13641](https://github.com/magento/magento2/pull/13641)) + * [#7515](https://github.com/magento/magento2/issues/7515) -- Error when submit customer/account/editPost form and session expired (fixed in [magento-engcom/magento2ce#1187](https://github.com/magento-engcom/magento2ce/pull/1187)) + * [#12404](https://github.com/magento/magento2/issues/12404) -- Output of setup:static-content:deploy contains red color, should be a friendlier color (fixed in [magento/magento2#13709](https://github.com/magento/magento2/pull/13709)) + * [#13006](https://github.com/magento/magento2/issues/13006) -- Drop down values are not showing in catalog product grid magento2 (fixed in [magento/magento2#13861](https://github.com/magento/magento2/pull/13861)) + * [#13899](https://github.com/magento/magento2/issues/13899) -- Postal code (zip code) for Canada should allow postal codes without space (fixed in [magento/magento2#13930](https://github.com/magento/magento2/pull/13930)) +* GitHub pull requests: + * [magento/magento2#12171](https://github.com/magento/magento2/pull/12171) -- 7691: address with saveInAddressBook 0 are still being added to the address book for new customers(backport to 2.2) (by @RomaKis) + * [magento/magento2#12239](https://github.com/magento/magento2/pull/12239) -- Fixed php notice when invalid ui_component config is used (by @vovayatsyuk) + * [magento/magento2#11407](https://github.com/magento/magento2/pull/11407) -- Added CLI command to enable and disable the Profiler (by @peterjaap) + * [magento/magento2#12257](https://github.com/magento/magento2/pull/12257) -- Phpdoc improvements (by @KarlDeux) + * [magento/magento2#11997](https://github.com/magento/magento2/pull/11997) -- 11941: Invoice for products that use qty decimal rounds down to whole number. (by @nmalevanec) + * [magento/magento2#12283](https://github.com/magento/magento2/pull/12283) -- magento/magento2#12083: Cannot import zero (0) value into custom attribute (by @p-bystritsky) + * [magento/magento2#12296](https://github.com/magento/magento2/pull/12296) -- Issue: 3596. Resolve Notice with undefined index 'value' (by @madonzy) + * [magento/magento2#12303](https://github.com/magento/magento2/pull/12303) -- 9764: exception message is wrong and misleading in findAccessorMethodName() of Magento\Framework\Reflection\NameFinder (by @RomaKis) + * [magento/magento2#12304](https://github.com/magento/magento2/pull/12304) -- Handle empty or incorrect lines in a language CSV (by @FreekVandeursen) + * [magento/magento2#12276](https://github.com/magento/magento2/pull/12276) -- Webshop throws an exception when sharing wishlist with RSS enabled (by @mediactbv) + * [magento/magento2#12310](https://github.com/magento/magento2/pull/12310) -- Fix robots.txt content type to 'text/plain' (by @tufahu) + * [magento/magento2#12332](https://github.com/magento/magento2/pull/12332) -- 9684: No ACL set for integrations (by @RomaKis) + * [magento/magento2#11787](https://github.com/magento/magento2/pull/11787) -- Fix #10438: Potential error on order edit page when address has extension attributes (by @joni-jones) + * [magento/magento2#12003](https://github.com/magento/magento2/pull/12003) -- magento/magento2#11691: Wrong return type for getAttributeText($attributeCode) (by @p-bystritsky) + * [magento/magento2#12308](https://github.com/magento/magento2/pull/12308) -- 12261: Order confirmation email contains non functioning links #12261 (by @RomaKis) + * [magento/magento2#12302](https://github.com/magento/magento2/pull/12302) -- Fixed 'Non-numeric value' warning on account create/save when DOB field is visible (by @vovayatsyuk) + * [magento/magento2#11320](https://github.com/magento/magento2/pull/11320) -- Fix email not sent when sitemap generation has errors (by @marinagociu) + * [magento/magento2#11429](https://github.com/magento/magento2/pull/11429) -- Magento 2.2.0 A solution for Product Repeat Issue after filter on category listing page. (by @mayankzalavadia) + * [magento/magento2#11550](https://github.com/magento/magento2/pull/11550) -- Even existing credit memos should be refundable if their state is open (by @ajpevers) + * [magento/magento2#11809](https://github.com/magento/magento2/pull/11809) -- 8003: Using System Value for Base Currency Results in Config Error. (by @nmalevanec) + * [magento/magento2#11592](https://github.com/magento/magento2/pull/11592) -- Fix issue #10347 - Wrong order tax amounts displayed when using specific tax configuration (2.2-develop) (by @PieterCappelle) + * [magento/magento2#11539](https://github.com/magento/magento2/pull/11539) -- Fix depends field not working for radio elements (by @jahvi) + * [magento/magento2#11846](https://github.com/magento/magento2/pull/11846) -- Fixed a js bug where ui_component labels have the wrong sort order. (by @deiserh) + * [magento/magento2#11965](https://github.com/magento/magento2/pull/11965) -- 11792: Can't add customizable options to product (by @RomaKis) + * [magento/magento2#12048](https://github.com/magento/magento2/pull/12048) -- #11528 can't save customizable options (by @luismiguelyangehuaman) + * [magento/magento2#12108](https://github.com/magento/magento2/pull/12108) -- 12064: Database Rollback not working with magento 2.1.9? (by @RomaKis) + * [magento/magento2#12387](https://github.com/magento/magento2/pull/12387) -- Update CAPTCHA labels to reflect the symbols in the CAPTCHA image (by @RhodriOwainDavies) + * [magento/magento2#12120](https://github.com/magento/magento2/pull/12120) -- Update AbstractBackend.php (by @hewersonfreitas) + * [magento/magento2#12154](https://github.com/magento/magento2/pull/12154) -- Add link to issue gates wiki page in the labels section of the readme (by @dmanners) + * [magento/magento2#11422](https://github.com/magento/magento2/pull/11422) -- [Backport 2.2] Translate order getCreatedAtFormatted() to store locale (by @JeroenVanLeusden) + * [magento/magento2#11473](https://github.com/magento/magento2/pull/11473) -- Fix for remove 'product_list_toolbar' block from layout in XML #9413 (by @mariuscris) + * [magento/magento2#11670](https://github.com/magento/magento2/pull/11670) -- save invoice ID on credit memo when using API method salesRefundInvoiceV1 (by @ajpevers) + * [magento/magento2#11992](https://github.com/magento/magento2/pull/11992) -- 11740: Sending emails from Admin in Multi-Store Environment defaults to Primary Store (by @RomaKis) + * [magento/magento2#12036](https://github.com/magento/magento2/pull/12036) -- Add swatch option: Prevent loosing data and default value if data is not populated via adminhtml (by @gomencal) + * [magento/magento2#12227](https://github.com/magento/magento2/pull/12227) -- Shipping method fixtures not compatible with getShippingMethod(true) in OrderCreateTest (by @andrew-garside-temando) + * [magento/magento2#12241](https://github.com/magento/magento2/pull/12241) -- 10128: New Orders not being saved to order grid (by @RomaKis) + * [magento/magento2#12132](https://github.com/magento/magento2/pull/12132) -- 10210: Transport variable can not be altered in email_invoice_set_template_vars_before Event (backport MAGETWO-69482 to 2.2) (by @RomaKis) + * [magento/magento2#11389](https://github.com/magento/magento2/pull/11389) -- Attribute category_ids issue (by @manuelson) + * [magento/magento2#12133](https://github.com/magento/magento2/pull/12133) -- Fix for issue 12127: Single quotation marks are now decoded properly in admin attribute option input fields (by @erfanimani) + * [magento/magento2#12253](https://github.com/magento/magento2/pull/12253) -- New validation: 3bytes characters filter (4 bytes characters cannot be stored using UTF8) (by @KarlDeux) + * [magento/magento2#12328](https://github.com/magento/magento2/pull/12328) -- 9742: Default welcome message returns after being deleted #9742 (by @RomaKis) + * [magento/magento2#12057](https://github.com/magento/magento2/pull/12057) -- [Backport] magento/magento2#9961: Unused product attributes display with value N/A or NO on storefront. (by @p-bystritsky) + * [magento/magento2#12441](https://github.com/magento/magento2/pull/12441) -- Add command "app:config:status" to check if "app:config:import" needed (by @jalogut) + * [magento/magento2#12443](https://github.com/magento/magento2/pull/12443) -- Fixed missing 'size' and 'type' props on a third-party category images [Backport 2.2] (by @vovayatsyuk) + * [magento/magento2#12495](https://github.com/magento/magento2/pull/12495) -- Fixed invalid parameter type in phpdoc block in Topmenu class (by @vovayatsyuk) + * [magento/magento2#11323](https://github.com/magento/magento2/pull/11323) -- Defaulting missing alt-text for a product to use the product name. (by @brobie) + * [magento/magento2#11388](https://github.com/magento/magento2/pull/11388) -- Fix #11236: Web Setup Wizard Icon Inconsistency (by @dverkade) + * [magento/magento2#11485](https://github.com/magento/magento2/pull/11485) -- do the stock check on default level because the stock on website leve… (by @joost-florijn-kega) + * [magento/magento2#11926](https://github.com/magento/magento2/pull/11926) -- 8255: Export Products action doesn't consider hide_for_product_page value. (by @nmalevanec) + * [magento/magento2#12207](https://github.com/magento/magento2/pull/12207) -- 11882: It's not possible to enable "log to file" (debugging) in production mode. Psr logger debug method does not work by the default in developer mode. (by @nmalevanec) + * [magento/magento2#11052](https://github.com/magento/magento2/pull/11052) -- Keep maintenance mode on if it was previously enabled (by @jokeputs) + * [magento/magento2#12038](https://github.com/magento/magento2/pull/12038) -- #11825: Generate new FormKey and replace for oldRequestParams Wishlist (by @osrecio) + * [magento/magento2#12161](https://github.com/magento/magento2/pull/12161) -- Fix delay initialization options for customized JQuery UI menu widget (by @scazz010) + * [magento/magento2#12466](https://github.com/magento/magento2/pull/12466) -- Category page X-Magento-Tags headers contains product cache identities even which category display mode is set to "Static block only" (by @atishgoswami) + * [magento/magento2#12515](https://github.com/magento/magento2/pull/12515) -- The left and the right parts of assignment are equal (by @lfluvisotto) + * [magento/magento2#12499](https://github.com/magento/magento2/pull/12499) -- Format generated config files using the short array syntax (by @cykirsch) + * [magento/magento2#12513](https://github.com/magento/magento2/pull/12513) -- Duplicate array key (by @lfluvisotto) + * [magento/magento2#12516](https://github.com/magento/magento2/pull/12516) -- Case mismatch (by @lfluvisotto) + * [magento/magento2#11444](https://github.com/magento/magento2/pull/11444) -- [Backport 2.2-develop] #11324 REST API - Only associate automatically product with all websites when creating product in All Store Views scope (by @adrian-martinez-interactiv4) + * [magento/magento2#11608](https://github.com/magento/magento2/pull/11608) -- Fix for issue 9633 500 error on setup wizard with memcache (by @sylink) + * [magento/magento2#11617](https://github.com/magento/magento2/pull/11617) -- Re saving product attribute (by @raumatbel) + * [magento/magento2#12359](https://github.com/magento/magento2/pull/12359) -- Add a --no-update option to sampledata:deploy and sampledata:remove commands (by @schmengler) + * [magento/magento2#12530](https://github.com/magento/magento2/pull/12530) -- Added correction for og:type content value (by @atishgoswami) + * [magento/magento2#11099](https://github.com/magento/magento2/pull/11099) -- Fix syntax of expectException() calls (by @schmengler) + * [magento/magento2#11435](https://github.com/magento/magento2/pull/11435) -- [Backport 2.2-develop] #11409: Too many password reset requests even when disabled in settings (by @adrian-martinez-interactiv4) + * [magento/magento2#12122](https://github.com/magento/magento2/pull/12122) -- [2.2] - Add command to view mview state and queue (by @convenient) + * [magento/magento2#12167](https://github.com/magento/magento2/pull/12167) -- 12110: Missing cascade into attribute set deletion. (by @nmalevanec) + * [magento/magento2#12469](https://github.com/magento/magento2/pull/12469) -- Added namespace to product videos fotorama events (by @roma84) + * [magento/magento2#12507](https://github.com/magento/magento2/pull/12507) -- Issue 12506: Fixup typo getDispretionPath -> getDispersionPath (by @PascalBrouwers) + * [magento/magento2#12539](https://github.com/magento/magento2/pull/12539) -- Trying to get data from non existent products (by @angelo983) + * [magento/magento2#12541](https://github.com/magento/magento2/pull/12541) -- [Backport 2.2-develop] Fix swagger-ui on instances of Magento running on a non-standard port (by @JeroenVanLeusden) + * [magento/magento2#12220](https://github.com/magento/magento2/pull/12220) -- 12180 Remove unnecessary use operator for Context, causes 503 error i… (by @chris-pook) + * [magento/magento2#12477](https://github.com/magento/magento2/pull/12477) -- NewRelic: Disables Module Deployments, Creates new Deploy Marker Command (by @fooman) + * [magento/magento2#12529](https://github.com/magento/magento2/pull/12529) -- #12450: Set Current Store from Store Code if isUseStoreInUrl (by @osrecio) + * [magento/magento2#12606](https://github.com/magento/magento2/pull/12606) -- Fix error loading theme configuration on PHP 7.2 (by @Alanaktion) + * [magento/magento2#12610](https://github.com/magento/magento2/pull/12610) -- Update CrontabManager.php (by @WaPoNe) + * [magento/magento2#12639](https://github.com/magento/magento2/pull/12639) -- Remove @escapeNotVerified from documentation (by @mzeis) + * [magento/magento2#11702](https://github.com/magento/magento2/pull/11702) -- Fix getReservedOrderId() to use current store instead of default store (by @tdgroot) + * [magento/magento2#12633](https://github.com/magento/magento2/pull/12633) -- Magento Connect no longer exist (by @miguelbalparda) + * [magento/magento2#12063](https://github.com/magento/magento2/pull/12063) -- 11946: Layer navigation showing wrong product count (by @RomaKis) + * [magento/magento2#12661](https://github.com/magento/magento2/pull/12661) -- [2.2-develop] Fixes #12660 invalid parameter configuration provided for argument (by @Tomasz-Silpion) + * [magento/magento2#11563](https://github.com/magento/magento2/pull/11563) -- Add price calculation improvement for product option value price (by @marinagociu) + * [magento/magento2#12666](https://github.com/magento/magento2/pull/12666) -- Fix incorrect DHL Product codes (by @gwharton) + * [magento/magento2#12723](https://github.com/magento/magento2/pull/12723) -- [2.2 Backport] Create CODE_OF_CONDUCT.md (by @ishakhsuvarov) + * [magento/magento2#11070](https://github.com/magento/magento2/pull/11070) -- Remove deprecation without alternative (by @schmengler) + * [magento/magento2#12730](https://github.com/magento/magento2/pull/12730) -- 12713 (by @EfremovaVI) + * [magento/magento2#12743](https://github.com/magento/magento2/pull/12743) -- #9453 - ported down c2e5d77a9516c8305585e819c2f0a0629648cc14 (by @strell) + * [magento/magento2#12747](https://github.com/magento/magento2/pull/12747) -- magento/magento2#9720 Menu item dependencies (dependsOnModule, depend… (by @hannassy) + * [magento/magento2#12767](https://github.com/magento/magento2/pull/12767) -- magento/magento2#12699: Multiselect Attribute is not saved (by @awarche) + * [magento/magento2#12786](https://github.com/magento/magento2/pull/12786) -- Fix typo in SINGLE_PRODUCT_LAYOUT_HANLDE (by @aschrammel) + * [magento/magento2#12630](https://github.com/magento/magento2/pull/12630) -- Add customer login url from Customer Url model to checkout config so … (by @quisse) + * [magento/magento2#12732](https://github.com/magento/magento2/pull/12732) -- Fix issue when tracking link returns 404 page in admin panel (by @ihor-sviziev) + * [magento/magento2#12739](https://github.com/magento/magento2/pull/12739) -- magento/magento2#6113: Validate range-words in Form component (UI Component) (by @Zamoroka) + * [magento/magento2#12738](https://github.com/magento/magento2/pull/12738) -- magento/magento2#12719: Use full name in welcome message (by @xpoback) + * [magento/magento2#12758](https://github.com/magento/magento2/pull/12758) -- magento/magento2#5035 Cannot subscribe to events with a number in name (by @Mobecls) + * [magento/magento2#12759](https://github.com/magento/magento2/pull/12759) -- Fix Back to Sign in url on confirmation form (by @StasKozar) + * [magento/magento2#12810](https://github.com/magento/magento2/pull/12810) -- Stop the profiler when returning early in \Magento\Eav\Model\Config::getAttribute (by @nicka101) + * [magento/magento2#12826](https://github.com/magento/magento2/pull/12826) -- Fix PhpDoc to show correct parameter types (by @FreekVandeursen) + * [magento/magento2#11462](https://github.com/magento/magento2/pull/11462) -- #7241: Always add empty option for prefix and/or suffix if optional (by @avstudnitz) + * [magento/magento2#11686](https://github.com/magento/magento2/pull/11686) -- Fix error when generating urn catalog for empty misc.xml (by @tdgroot) + * [magento/magento2#11878](https://github.com/magento/magento2/pull/11878) -- [BUGFIX] Made method public so a plugin is possible. (by @dheesbeen) + * [magento/magento2#12105](https://github.com/magento/magento2/pull/12105) -- #11936:required attribute set id filter on attribute group repository getList (by @tzyganu) + * [magento/magento2#12636](https://github.com/magento/magento2/pull/12636) -- #12625: Add Current Date to update_time Field for Block and Pages (by @osrecio) + * [magento/magento2#12737](https://github.com/magento/magento2/pull/12737) -- magento/magento2#11953: Product configuration creator does not warn about invalid SKUs (by @Zamoroka) + * [magento/magento2#12751](https://github.com/magento/magento2/pull/12751) -- magento/magento2#12439 Newsletter subscription success email not sent… (by @Styopchik) + * [magento/magento2#12884](https://github.com/magento/magento2/pull/12884) -- [Backport 2.2] Update functional.suite.dist.yml to handle a custom backend name (by @scribam) + * [magento/magento2#12734](https://github.com/magento/magento2/pull/12734) -- #6916 Fix notice during Update Bundle Product without changes (by @dzianis-yurevich) + * [magento/magento2#12859](https://github.com/magento/magento2/pull/12859) -- Throw ValidationException for invalid xml (by @pmclain) + * [magento/magento2#12875](https://github.com/magento/magento2/pull/12875) -- Add more parameters to ajax:addToCart (by @srenon) + * [magento/magento2#12736](https://github.com/magento/magento2/pull/12736) -- Issues/12374 (by @virtual97) + * [magento/magento2#12401](https://github.com/magento/magento2/pull/12401) -- Correctly set payment information when using paypal (by @therool) + * [magento/magento2#12768](https://github.com/magento/magento2/pull/12768) -- magento/magento2: Missing ext-bcmath dependency added (by @Mobecls) + * [magento/magento2#12845](https://github.com/magento/magento2/pull/12845) -- Add missing preference for ObjectManager\ConfigInterface in integrati… (by @schmengler) + * [magento/magento2#12857](https://github.com/magento/magento2/pull/12857) -- Update progress.phtml (by @jonashrem) + * [magento/magento2#12887](https://github.com/magento/magento2/pull/12887) -- Remove unused if statement in order invoice save (by @JeroenVanLeusden) + * [magento/magento2#12931](https://github.com/magento/magento2/pull/12931) -- Display scroll bar of admin store switcher in OSX computers. (by @jalogut) + * [magento/magento2#12946](https://github.com/magento/magento2/pull/12946) -- Respect "Learn More Link" in Recently Viewed Products widget options (by @JeroenVanLeusden) + * [magento/magento2#12951](https://github.com/magento/magento2/pull/12951) -- [Bug] Correctly construct Magento\Framework\Phrase (by @punkstar) + * [magento/magento2#12755](https://github.com/magento/magento2/pull/12755) -- magento/magento2#12294: Bug: Adding Custom Attribute - The value of A… (by @virtual97) + * [magento/magento2#12902](https://github.com/magento/magento2/pull/12902) -- Fix #12900: Braintree "Place Order" button is disabled after failed validation (by @joni-jones) + * [magento/magento2#12945](https://github.com/magento/magento2/pull/12945) -- Naming collision in Javascript ui registry (backend) to 2.2 (by @VladimirZaets) + * [magento/magento2#12521](https://github.com/magento/magento2/pull/12521) -- Match flexible static file version in nginx sample config (by @scottsb) + * [magento/magento2#12752](https://github.com/magento/magento2/pull/12752) -- magento/magento2#4292: Ability to sitch to default mode (by @Etty) + * [magento/magento2#12953](https://github.com/magento/magento2/pull/12953) -- [Backport to 2.2-develop] Fix #2156 Js\Dataprovider uses the RendererInterface. (by @dverkade) + * [magento/magento2#12963](https://github.com/magento/magento2/pull/12963) -- Sort configurable attribute options by sort_order (by @wardcapp) + * [magento/magento2#12862](https://github.com/magento/magento2/pull/12862) -- Change _getHtml to append class rather than overwrite for children (by @jonshipman) + * [magento/magento2#13015](https://github.com/magento/magento2/pull/13015) -- [Backport to 2.2-develop] The quote address fields length expanded in the database (by @dverkade) + * [magento/magento2#13027](https://github.com/magento/magento2/pull/13027) -- Change of copyright year from 2017 to 2018. (by @bhargavmehta) + * [magento/magento2#12649](https://github.com/magento/magento2/pull/12649) -- #12446: Add GetUtilityPageIdentifiers for Manage Custom Pages to be excluded … (by @osrecio) + * [magento/magento2#12917](https://github.com/magento/magento2/pull/12917) -- Fix issue 12894: Can't remove State is required for all countries (by @vasilii-b) + * [magento/magento2#12922](https://github.com/magento/magento2/pull/12922) -- Handle multiple errors in customer address validation when shown in adminhtml customer edit page (by @adrian-martinez-interactiv4) + * [magento/magento2#13019](https://github.com/magento/magento2/pull/13019) -- [Backport to 2.2-develop] Attribute with "Catalog Input Type for Store Owner" equal "Fixed Product Tax" for Multi-store (by @dverkade) + * [magento/magento2#13052](https://github.com/magento/magento2/pull/13052) -- Make "top destinations" config field configurable on store level (by @avstudnitz) + * [magento/magento2#12901](https://github.com/magento/magento2/pull/12901) -- FIX: remove not used count() from templates (by @Coderimus) + * [magento/magento2#13050](https://github.com/magento/magento2/pull/13050) -- Updated cron documentation URL to 2.2 (by @robbie-thompson) + * [magento/magento2#11369](https://github.com/magento/magento2/pull/11369) -- Database backup doesn't include triggers #9036 (by @denisristic) + * [magento/magento2#12731](https://github.com/magento/magento2/pull/12731) -- magento/magento2#12209: Substitution payment method - Incorrect message (by @Zamoroka) + * [magento/magento2#12964](https://github.com/magento/magento2/pull/12964) -- Add trim filter to first, middle and lastname. (by @wardcapp) + * [magento/magento2#12985](https://github.com/magento/magento2/pull/12985) -- Fix jumping content on page reload in admin area (by @avoelkl) + * [magento/magento2#13026](https://github.com/magento/magento2/pull/13026) -- Feature space between category page (by @sanjay-wagento) + * [magento/magento2#13041](https://github.com/magento/magento2/pull/13041) -- Solution For Newsletter subscribe button title wrapped (by @monaemipro) + * [magento/magento2#13051](https://github.com/magento/magento2/pull/13051) -- Fix JS error on cart from postcode validation when 'US' is deselected as an allowed country (by @codekipple) + * [magento/magento2#13076](https://github.com/magento/magento2/pull/13076) -- Fix issues caused by using continue in loops (by @ihor-sviziev) + * [magento/magento2#13029](https://github.com/magento/magento2/pull/13029) -- Newsletter Label is broking on chinese Language like 订阅 (by @dasharath-wagento) + * [magento/magento2#12965](https://github.com/magento/magento2/pull/12965) -- Fix vault_payment_token install script type where column defaults were not set (by @helloitsluke) + * [magento/magento2#13030](https://github.com/magento/magento2/pull/13030) -- Resolved Checkout-Payment-Wrong promo code cancelled issue (by @chiragp-wagento) + * [magento/magento2#13039](https://github.com/magento/magento2/pull/13039) -- Feature minimum order amount notice issue (by @neeta-wagento) + * [magento/magento2#13061](https://github.com/magento/magento2/pull/13061) -- Fix for requireJS loading issues (for ad blockers) (by @Yonn-Trimoreau) + * [magento/magento2#13081](https://github.com/magento/magento2/pull/13081) -- Fix for #11796 Magento2.2.0 home page product grid issues (by @punitv) + * [magento/magento2#13084](https://github.com/magento/magento2/pull/13084) -- Fixed magnifier issue. (by @mayankzalavadia) + * [magento/magento2#12668](https://github.com/magento/magento2/pull/12668) -- Fix for reverting stock twice for cancelled orders (by @dverkade) + * [magento/magento2#13034](https://github.com/magento/magento2/pull/13034) -- Magento 2.2 Develop fix for #12221 Google Analytics Pageview Triggered twice (by @bhargavmehta) + * [magento/magento2#13036](https://github.com/magento/magento2/pull/13036) -- magento/magento2#12705: Integrity constraint violation error after re… (by @vinayshah) + * [magento/magento2#13044](https://github.com/magento/magento2/pull/13044) -- Fix Newsletter Subscribe Workflow (by @torhoehn) + * [magento/magento2#13161](https://github.com/magento/magento2/pull/13161) -- Updated README file to take resources from 2.2 instead of 2.0. (by @bhargavmehta) + * [magento/magento2#12990](https://github.com/magento/magento2/pull/12990) -- [2.2.x] Fix undeclared dependency magento/zendframework1 by magento/framework (by @ihor-sviziev) + * [magento/magento2#12998](https://github.com/magento/magento2/pull/12998) -- Make customer name link to customer dashboard (by @srenon) + * [magento/magento2#13033](https://github.com/magento/magento2/pull/13033) -- Newsletter\Model\Subscriber::loadByEmail() does not use MySQL index (by @devamitbera) + * [magento/magento2#13066](https://github.com/magento/magento2/pull/13066) -- Fix for #12877 as per @azeemism (by @jagritijoshi) + * [magento/magento2#13086](https://github.com/magento/magento2/pull/13086) -- Add failsafe to items.phtml (by @samgranger) + * [magento/magento2#13169](https://github.com/magento/magento2/pull/13169) -- Optimization: magento/module-eav is_null change to strict comparison … (by @Coderimus) + * [magento/magento2#13170](https://github.com/magento/magento2/pull/13170) -- Optimization: magento/module-tax is_null change to strict comparison (by @Coderimus) + * [magento/magento2#13155](https://github.com/magento/magento2/pull/13155) -- Optimization: module-sales is_null change to strict comparison instead (by @Coderimus) + * [magento/magento2#13171](https://github.com/magento/magento2/pull/13171) -- Optimization: magento/module-catalog is_null change to strict comparison (by @Coderimus) + * [magento/magento2#13174](https://github.com/magento/magento2/pull/13174) -- Fix: remove TestObserver class (by @Coderimus) + * [magento/magento2#12807](https://github.com/magento/magento2/pull/12807) -- Reorder adding of page layout handles (by @aschrammel) + * [magento/magento2#13101](https://github.com/magento/magento2/pull/13101) -- 11828 Fix issue with swatch colour block not showing in admin panel once colour selected (PHP7.1.x issue). (by @chris-pook) + * [magento/magento2#13082](https://github.com/magento/magento2/pull/13082) -- Fix Magento_Checkout address formatting (by @nfourteen) + * [magento/magento2#13141](https://github.com/magento/magento2/pull/13141) -- Fix missing discount label in checkout (by @ihor-sviziev) + * [magento/magento2#13025](https://github.com/magento/magento2/pull/13025) -- fixed issue prices aren't readable when using custom price symbol (by @pradeep-wagento) + * [magento/magento2#13208](https://github.com/magento/magento2/pull/13208) -- #12714 - pass parameter for export button url (by @sanjay-wagento) + * [magento/magento2#12406](https://github.com/magento/magento2/pull/12406) -- Issue/12342/js test driver removal (by @KarlDeux) + * [magento/magento2#13310](https://github.com/magento/magento2/pull/13310) -- Add the domReady! statement (by @arnoudhgz) + * [magento/magento2#13324](https://github.com/magento/magento2/pull/13324) -- Alignement Array assignement (by @Nolwennig) + * [magento/magento2#12936](https://github.com/magento/magento2/pull/12936) -- FIX: out-of-stock options for configurable product visible on frontend as sellable (by @Coderimus) + * [magento/magento2#11060](https://github.com/magento/magento2/pull/11060) -- Handle transparncy correctly for watermark (by @elzekool) + * [magento/magento2#13408](https://github.com/magento/magento2/pull/13408) -- Translate time zone label according to current locale in Stores > Configuration > Advanced Reporting (by @adrian-martinez-interactiv4) + * [magento/magento2#12650](https://github.com/magento/magento2/pull/12650) -- Add fallback for Product_links position attribute if not set in request (by @mohammedsalem) + * [magento/magento2#13341](https://github.com/magento/magento2/pull/13341) -- Bugfix/13327 ui active state not removed from previous menu item (by @arnoudhgz) + * [magento/magento2#13364](https://github.com/magento/magento2/pull/13364) -- [Backport 2.2] In checkout->multishipping-> new addres clean region when select country without dropdown for states (by @enriquei4) + * [magento/magento2#13373](https://github.com/magento/magento2/pull/13373) -- Edited doc block of the walk method in a Collection (by @ByteCreation) + * [magento/magento2#13436](https://github.com/magento/magento2/pull/13436) -- Product Link Save Handler - Remove not used constructor dependency (by @ihor-sviziev) + * [magento/magento2#13449](https://github.com/magento/magento2/pull/13449) -- Fix default discount tax calculation in double (by @VincentMarmiesse) + * [magento/magento2#13450](https://github.com/magento/magento2/pull/13450) -- Removed each function usage (by @ihor-sviziev) + * [magento/magento2#13485](https://github.com/magento/magento2/pull/13485) -- Update code formatting in Swagger Block (by @JeroenVanLeusden) + * [magento/magento2#13132](https://github.com/magento/magento2/pull/13132) -- Update the Emogrifier dependency to ^2.0.0 (by @oliverklee) + * [magento/magento2#13494](https://github.com/magento/magento2/pull/13494) -- Fixing of Problem with updating stock item qty and stock status (by @nuzil) + * [magento/magento2#13498](https://github.com/magento/magento2/pull/13498) -- issue #13497 - Method getUrl in Magento\Catalog\Model\Product\Attribu… (by @igortregub) + * [magento/magento2#13040](https://github.com/magento/magento2/pull/13040) -- magento/magento2#: Customer Login/Logout Issue (by @vinayshah) + * [magento/magento2#13462](https://github.com/magento/magento2/pull/13462) -- Switch updatecart qty input validators to dynamic instead of hardcoding (by @gil--) + * [magento/magento2#13528](https://github.com/magento/magento2/pull/13528) -- Fix for #12081: missing translations in the js-translations.json (by @mattijv) + * [magento/magento2#13563](https://github.com/magento/magento2/pull/13563) -- magento/magento2#11252: fix adminhtml file attribute edit form (by @Mkennethsmith) + * [magento/magento2#13551](https://github.com/magento/magento2/pull/13551) -- Fix json encoded attribute backend type to not encode attribute value multiple times (by @tkotosz) + * [magento/magento2#12843](https://github.com/magento/magento2/pull/12843) -- Display a more meaningful error message in case of misspelt module name (by @JanisE) + * [magento/magento2#13438](https://github.com/magento/magento2/pull/13438) -- Product image builder - Override attributes when builder used multiple times (by @ihor-sviziev) + * [magento/magento2#13596](https://github.com/magento/magento2/pull/13596) -- Fix adding values to system variable collection (by @mszydlo) + * [magento/magento2#13614](https://github.com/magento/magento2/pull/13614) -- Show redirect_to_base config in store scope (by @JeroenVanLeusden) + * [magento/magento2#11504](https://github.com/magento/magento2/pull/11504) -- Add MagentoStyle as Console Input/output helper object... (by @wesleywmd) + * [magento/magento2#13587](https://github.com/magento/magento2/pull/13587) -- Show maintenance IP-address without commas (by @barryvdh) + * [magento/magento2#13679](https://github.com/magento/magento2/pull/13679) -- Update StorageInterface.php (by @davidangel) + * [magento/magento2#13663](https://github.com/magento/magento2/pull/13663) -- Refactoring: remove unuseful temporary variable (by @real34) + * [magento/magento2#13698](https://github.com/magento/magento2/pull/13698) -- [Travis Test Fix] Add MagentoStyle as Console Input/output (by @magento-engcom-team) + * [magento/magento2#13586](https://github.com/magento/magento2/pull/13586) -- Add option to add IP address to existing list (by @barryvdh) + * [magento/magento2#13643](https://github.com/magento/magento2/pull/13643) -- Fixes #12791 - Use a selector to only select the correct tax rate sel… (by @hostep) + * [magento/magento2#13661](https://github.com/magento/magento2/pull/13661) -- Typo (address not addres) (by @srenon) + * [magento/magento2#13678](https://github.com/magento/magento2/pull/13678) -- Add RewriteBase directive template in .htaccess file into pub/static folder (by @ccasciotti) + * [magento/magento2#13740](https://github.com/magento/magento2/pull/13740) -- Display a more meaningful error message in case of misspelt module name unit test. (by @nmalevanec) + * [magento/magento2#13742](https://github.com/magento/magento2/pull/13742) -- Fix adding values to system variable collection unit test. (by @nmalevanec) + * [magento/magento2#13761](https://github.com/magento/magento2/pull/13761) -- Fix bug Magento 2.2.2 password reset strength meter #13429 (by @aoldoni) + * [magento/magento2#13759](https://github.com/magento/magento2/pull/13759) -- Add ObserverInterface to the api (by @fooman) + * [magento/magento2#13770](https://github.com/magento/magento2/pull/13770) -- Remove not-allowed currencies from the currencies dropdown in Setup (by @r-martins) + * [magento/magento2#12749](https://github.com/magento/magento2/pull/12749) -- Grid filtration doesn't work for mysql special characters (by @laconica-sergey) + * [magento/magento2#13280](https://github.com/magento/magento2/pull/13280) -- Add option "lock-config" for shell command "config:set" (by @avstudnitz) + * [magento/magento2#13584](https://github.com/magento/magento2/pull/13584) -- Ensure DeploymentConfig Reader always returns an array (by @barryvdh) + * [magento/magento2#13680](https://github.com/magento/magento2/pull/13680) -- Cast handling fee to float (by @schmengler) + * [magento/magento2#13762](https://github.com/magento/magento2/pull/13762) -- Remove forced setting of cache_lifetime to false in constructor and set default cache_lifetime to 3600 (by @zolat) + * [magento/magento2#12564](https://github.com/magento/magento2/pull/12564) -- Add visibility and status filter to category product grid (by @peterjaap) + * [magento/magento2#13682](https://github.com/magento/magento2/pull/13682) -- [Backport-2.2] of PR-#10935 Fix LowStock report in All Websites view (by @gwharton) + * [magento/magento2#13700](https://github.com/magento/magento2/pull/13700) -- Fix faulty admin spinner animation (by @RNanoware) + * [magento/magento2#13777](https://github.com/magento/magento2/pull/13777) -- Fix #13315. Mobile 'Payments methods' step looks bad on mobile (by @Frodigo) + * [magento/magento2#13811](https://github.com/magento/magento2/pull/13811) -- Added missing event parameter for proxy function on the search form submit (by @koenner01) + * [magento/magento2#13816](https://github.com/magento/magento2/pull/13816) -- Add @api annotation to block argument marker interface (by @Vinai) + * [magento/magento2#13830](https://github.com/magento/magento2/pull/13830) -- Minicart should require dropdownDialog (by @amenk) + * [magento/magento2#13038](https://github.com/magento/magento2/pull/13038) -- Default Welcome message is broken on storefront with enabled translate-inline (by @pareshpansuriya) + * [magento/magento2#13567](https://github.com/magento/magento2/pull/13567) -- Add integration tests for product urls rewrite generation (by @adrien-louis-r) + * [magento/magento2#13787](https://github.com/magento/magento2/pull/13787) -- Issue-13768 Fixed error messages on admin user account page after redirect for force password change (by @nuzil) + * [magento/magento2#13817](https://github.com/magento/magento2/pull/13817) -- Allow changing head and body element through xml layout updates (by @cedricziel) + * [magento/magento2#13828](https://github.com/magento/magento2/pull/13828) -- Inconsistent Redirect in Admin Notification Controller (by @chickenland) + * [magento/magento2#13844](https://github.com/magento/magento2/pull/13844) -- Fix issue 13827 (by @julienanquetil) + * [magento/magento2#13897](https://github.com/magento/magento2/pull/13897) -- Fix typo in securityCheckers array (by @pmclain) + * [magento/magento2#13796](https://github.com/magento/magento2/pull/13796) -- Save CMS Block using repository (by @JeroenVanLeusden) + * [magento/magento2#13814](https://github.com/magento/magento2/pull/13814) -- Load CMS Page using repository in save action (by @JeroenVanLeusden) + * [magento/magento2#11513](https://github.com/magento/magento2/pull/11513) -- Modify Report processor to return 500 (by @andrewhowdencom) + * [magento/magento2#13914](https://github.com/magento/magento2/pull/13914) -- Pass Expected Data Type in backgroundColor Call (2.2) (by @northernco) + * [magento/magento2#13217](https://github.com/magento/magento2/pull/13217) -- Fix JS address converter function from mutating its argument (by @vaaralav) + * [magento/magento2#13641](https://github.com/magento/magento2/pull/13641) -- Add missing implementation for applySortOrder() (by @schmengler) + * [magento/magento2#13709](https://github.com/magento/magento2/pull/13709) -- Changes static content deploy log levels verbosity (by @hostep) + * [magento/magento2#13750](https://github.com/magento/magento2/pull/13750) -- Less clean up (by @Karlasa) + * [magento/magento2#13861](https://github.com/magento/magento2/pull/13861) -- Solved this issue : Drop down values are not showing in catalog produ… (by @hiren-wagento) + * [magento/magento2#13930](https://github.com/magento/magento2/pull/13930) -- #13899 Solve Canada Zip Code pattern (by @tadeobarranco) + * [magento/magento2#13966](https://github.com/magento/magento2/pull/13966) -- Setup Lists - Make allowedCurrencies property private (by @ihor-sviziev) + +2.2.2 +============= +* GitHub issues: + * [#9968](https://github.com/magento/magento2/issues/9968) -- Canceled invoice can be canceled again (fixed in [#11261](https://github.com/magento/magento2/pull/11261)) + * [#11310](https://github.com/magento/magento2/issues/11310) -- Method "getChildren" sort ordering (fixed in [#11342](https://github.com/magento/magento2/pull/11342)) + * [#11332](https://github.com/magento/magento2/issues/11332) -- How to Fix the wrong input format of Customer date of birth (fixed in [#11351](https://github.com/magento/magento2/pull/11351)) + * [#11207](https://github.com/magento/magento2/issues/11207) -- Shipment API won't append comment to email (fixed in [#11383](https://github.com/magento/magento2/pull/11383)) + * [#10795](https://github.com/magento/magento2/issues/10795) -- Shipping method radios have duplicate IDs on cart page (fixed in [#11406](https://github.com/magento/magento2/pull/11406)) + * [#10941](https://github.com/magento/magento2/issues/10941) -- Responsive Design Issue on Mobile with Magento 2.1.9 (fixed in [#11430](https://github.com/magento/magento2/pull/11430)) + * [#10007](https://github.com/magento/magento2/issues/10007) -- ProductAlert: Product alerts not showing in admin side product edit page (fixed in [#11445](https://github.com/magento/magento2/pull/11445)) + * [#10231](https://github.com/magento/magento2/issues/10231) -- Custom URL Rewrite Not working (fixed in [#11470](https://github.com/magento/magento2/pull/11470)) + * [#11176](https://github.com/magento/magento2/issues/11176) -- Configured table prefix is not recognized in CLI admin:user:create (fixed in [#11199](https://github.com/magento/magento2/pull/11199)) + * [#11275](https://github.com/magento/magento2/issues/11275) -- Call to a member function addCrumb() (fixed in [#11299](https://github.com/magento/magento2/pull/11299)) + * [#10441](https://github.com/magento/magento2/issues/10441) -- State/Province Not displayed after edit Billing Address on Sales Orders - Backend Admin. (fixed in [#11381](https://github.com/magento/magento2/pull/11381)) + * [#11140](https://github.com/magento/magento2/issues/11140) -- Going to '/admin' while using storecodes in url and a different adminhtml url will throw exception (fixed in [#11460](https://github.com/magento/magento2/pull/11460)) + * [#10765](https://github.com/magento/magento2/issues/10765) -- Export data from grid not adding custom rendered data magento2 (fixed in [#11437](https://github.com/magento/magento2/pull/11437)) + * [#7678](https://github.com/magento/magento2/issues/7678) -- StockItemCriteria::setProductsFilter doesn't work with array of ids (fixed in [#11500](https://github.com/magento/magento2/pull/11500)) + * [#9783](https://github.com/magento/magento2/issues/9783) -- Multiple parameters in widget.xml not allowed (fixed in [#11495](https://github.com/magento/magento2/pull/11495)) + * [#10824](https://github.com/magento/magento2/issues/10824) -- Cannot add new columns to item grid in admin sales_order_view layout (fixed in [#11235](https://github.com/magento/magento2/pull/11235)) + * [#9919](https://github.com/magento/magento2/issues/9919) -- Pattern Validation via UI Component Fails to Interpret String as RegEx Pattern (fixed in [#11565](https://github.com/magento/magento2/pull/11565)) + * [#5439](https://github.com/magento/magento2/issues/5439) -- Newsletter subscription (fixed in [#11317](https://github.com/magento/magento2/pull/11317)) + * [#10856](https://github.com/magento/magento2/issues/10856) -- Sync billing with shipping address on Admin Reorder and Admin Customer Create Order page does not work for Existing address selected (fixed in [#11385](https://github.com/magento/magento2/pull/11385)) + * [#10025](https://github.com/magento/magento2/issues/10025) -- Integration tests don't reset the database (fixed in [#11499](https://github.com/magento/magento2/pull/11499)) + * [#10301](https://github.com/magento/magento2/issues/10301) -- Customer review report search Bug in 2.1.x, 2.2 (fixed in [#11522](https://github.com/magento/magento2/pull/11522)) + * [#11540](https://github.com/magento/magento2/issues/11540) -- Magento sets iso invalid language code in html header (fixed in [#11561](https://github.com/magento/magento2/pull/11561)) + * [#11586](https://github.com/magento/magento2/issues/11586) -- Cron install / remove via command messes up stderr 2>&1 entries (fixed in [#11591](https://github.com/magento/magento2/pull/11591)) + * [#6350](https://github.com/magento/magento2/issues/6350) -- Frontend: Datepicker/calendar control does not use the store locale (fixed in [#11057](https://github.com/magento/magento2/pull/11057)) + * [#11328](https://github.com/magento/magento2/issues/11328) -- app:config:dump adds extra space every time in multiline array value (fixed in [#11439](https://github.com/magento/magento2/pull/11439)) + * [#7591](https://github.com/magento/magento2/issues/7591) -- PayPal module, "didgit" misspelling (fixed in [#11673](https://github.com/magento/magento2/pull/11673)) + * [#7767](https://github.com/magento/magento2/issues/7767) -- in system.xml translate phrase not work (fixed in [#11675](https://github.com/magento/magento2/pull/11675)) + * [#7915](https://github.com/magento/magento2/issues/7915) -- customer objects are equal to eachother after observing event customer_save_after_data_object (fixed in [#11676](https://github.com/magento/magento2/pull/11676)) + * [#10275](https://github.com/magento/magento2/issues/10275) -- Admin global search - submit by enter doesn't work (fixed in [#11250](https://github.com/magento/magento2/pull/11250)) + * [#11022](https://github.com/magento/magento2/issues/11022) -- GET v1/products/attribute-sets/sets/list inconsistent return result (fixed in [#11421](https://github.com/magento/magento2/pull/11421)) + * [#5956](https://github.com/magento/magento2/issues/5956) -- Untranslatable string "Please enter the same value again." (fixed in [#11440](https://github.com/magento/magento2/pull/11440)) + * [#9944](https://github.com/magento/magento2/issues/9944) -- Name attribute shows empty when creating custom fields on product creation form (fixed in [#11637](https://github.com/magento/magento2/pull/11637)) + * [#10168](https://github.com/magento/magento2/issues/10168) -- Coupon codes not showing in invoice (fixed in [#11635](https://github.com/magento/magento2/pull/11635)) + * [#9763](https://github.com/magento/magento2/issues/9763) -- When go checkout,Cart Price Rules 25%test coupon code can go wrong (fixed in [#11710](https://github.com/magento/magento2/pull/11710)) + * [#11157](https://github.com/magento/magento2/issues/11157) -- nginx.sample.conf missing heath_check.php? (fixed in [#11690](https://github.com/magento/magento2/pull/11690)) + * [#11322](https://github.com/magento/magento2/issues/11322) -- User.ini files specify 768M - Docs recommend at least 1G (fixed in [#11734](https://github.com/magento/magento2/pull/11734)) + * [#7927](https://github.com/magento/magento2/issues/7927) -- Dashboard graph has broken y-axis range (fixed in [#11751](https://github.com/magento/magento2/pull/11751)) + * [#7099](https://github.com/magento/magento2/issues/7099) -- Admin: field labels wrapping poorly (fixed in [#11745](https://github.com/magento/magento2/pull/11745)) + * [#9869](https://github.com/magento/magento2/issues/9869) -- datetime type product attribute showing current date (fixed in [#11749](https://github.com/magento/magento2/pull/11749)) + * [#11365](https://github.com/magento/magento2/issues/11365) -- "Ignore this notification" isn't working (fixed in [#11410](https://github.com/magento/magento2/pull/11410)) + * [#6891](https://github.com/magento/magento2/issues/6891) -- Add-to-cart checkbox still visible when $canItemsAddToCart = false (fixed in [#11610](https://github.com/magento/magento2/pull/11610)) + * [#11729](https://github.com/magento/magento2/issues/11729) -- Exported Excel with negative number can't be opened by MS Office (fixed in [#11757](https://github.com/magento/magento2/pull/11757)) + * [#6924](https://github.com/magento/magento2/issues/6924) -- Magento 2.1.0 - "General system exception happened" on Import .csv (fixed in [#11363](https://github.com/magento/magento2/pull/11363)) + * [#7640](https://github.com/magento/magento2/issues/7640) -- X-Magento-Tags header containing whitespaces causes exception (fixed in [#11767](https://github.com/magento/magento2/pull/11767)) + * [#4711](https://github.com/magento/magento2/issues/4711) -- Improve error reporting for products images import (fixed in [#11779](https://github.com/magento/magento2/pull/11779)) + * [#4696](https://github.com/magento/magento2/issues/4696) -- Admin product search - Pressing enter does not submit (fixed in [#11827](https://github.com/magento/magento2/pull/11827)) + * [#11581](https://github.com/magento/magento2/issues/11581) -- Reference to wrong / non-existing class (fixed in [#11830](https://github.com/magento/magento2/pull/11830)) + * [#10908](https://github.com/magento/magento2/issues/10908) -- [2.2.0-rc3.0] Language switcher is broken when using multiple times (fixed in [#11337](https://github.com/magento/magento2/pull/11337)) + * [#11211](https://github.com/magento/magento2/issues/11211) -- Store View switcher not working on front-end and it throws an error (fixed in [#11337](https://github.com/magento/magento2/pull/11337)) + * [#2991](https://github.com/magento/magento2/issues/2991) -- Products added to cart with REST API give total prices equal to zero (fixed in [#11458](https://github.com/magento/magento2/pull/11458)) + * [#10032](https://github.com/magento/magento2/issues/10032) -- Download back-up .tgz always takes the latest that's created (fixed in [#11595](https://github.com/magento/magento2/pull/11595)) + * [#11534](https://github.com/magento/magento2/issues/11534) -- Values of Visual Swatch Attribute drop down is not work correct (fixed in [#11747](https://github.com/magento/magento2/pull/11747)) + * [#10291](https://github.com/magento/magento2/issues/10291) -- Magento 2 Loading custom option dropdown issue (fixed in [#11824](https://github.com/magento/magento2/pull/11824)) + * [#11095](https://github.com/magento/magento2/issues/11095) -- Magento_Tax "postcode is a required field" when upgrading from 2.1.9 to 2.2 (fixed in [#11651](https://github.com/magento/magento2/pull/11651)) + * [#8236](https://github.com/magento/magento2/issues/8236) -- CMS blocks are not validated against having same store and identifier (fixed in [#11802](https://github.com/magento/magento2/pull/11802)) + * [#4808](https://github.com/magento/magento2/issues/4808) -- The price of product custom option can't be set to 0. (fixed in [#11843](https://github.com/magento/magento2/pull/11843)) + * [#9566](https://github.com/magento/magento2/issues/9566) -- Status label is wrong in admin (fixed in [#11397](https://github.com/magento/magento2/pull/11397)) + * [#5015](https://github.com/magento/magento2/issues/5015) -- Report error csv doesn't work when trying to import a csv file with semicolon delimiter (fixed in [#11732](https://github.com/magento/magento2/pull/11732)) + * [#10682](https://github.com/magento/magento2/issues/10682) -- Meta description and keywords transform to html entities for non latin/cyrilic characters in category and product pages (fixed in [#11829](https://github.com/magento/magento2/pull/11829)) + * [#10185](https://github.com/magento/magento2/issues/10185) -- New Orders are not in Order grid after data migration from M 1.7.0.2 to M 2.1.7 (fixed in [#11911](https://github.com/magento/magento2/pull/11911)) + * [#8970](https://github.com/magento/magento2/issues/8970) -- Cannot assign products to categories not under tree root (fixed in [#11817](https://github.com/magento/magento2/pull/11817)) + * [#9028](https://github.com/magento/magento2/issues/9028) -- You cannot set a 303 redirect response using a result factory (fixed in [#11405](https://github.com/magento/magento2/pull/11405)) + * [#11697](https://github.com/magento/magento2/issues/11697) -- Theme: Added html node to page xml root, cause validation error (fixed in [#11858](https://github.com/magento/magento2/pull/11858)) + * [#8954](https://github.com/magento/magento2/issues/8954) -- Error While Trying To Load Quote Item Collection Using Magento\Quote\Model\ResourceModel\QuoteItem\Collection::getItems() (fixed in [#11869](https://github.com/magento/magento2/pull/11869)) + * [#8799](https://github.com/magento/magento2/issues/8799) -- Image brackground (fixed in [#11889](https://github.com/magento/magento2/pull/11889)) + * [#11868](https://github.com/magento/magento2/issues/11868) -- "Add Products" button has been duplicated after the customer group was changed (fixed in [#11949](https://github.com/magento/magento2/pull/11949)) + * [#11898](https://github.com/magento/magento2/issues/11898) -- Zip code Netherlands should allow zipcode without space (fixed in [#11959](https://github.com/magento/magento2/pull/11959)) + * [#11996](https://github.com/magento/magento2/issues/11996) -- Magento 2 Store Code validation regex: doesn't support uppercase letters in store code (fixed in [#12011](https://github.com/magento/magento2/pull/12011)) + * [#7995](https://github.com/magento/magento2/issues/7995) -- If you leave as default, shipping lines disappear (fixed in [#12013](https://github.com/magento/magento2/pull/12013)) + * [#8846](https://github.com/magento/magento2/issues/8846) -- Attribute option value uniqueness is not checked if created via REST Api (fixed in [#11785](https://github.com/magento/magento2/pull/11785)) + * [#11700](https://github.com/magento/magento2/issues/11700) -- "Something Went Wrong" error for limited access admin user (fixed in [#11993](https://github.com/magento/magento2/pull/11993)) + * [#12017](https://github.com/magento/magento2/issues/12017) -- Cross-sell product placeholder image size issue (fixed in [#12018](https://github.com/magento/magento2/pull/12018)) + * [#10583](https://github.com/magento/magento2/issues/10583) -- Checkout place order exception when using a new address (fixed in [#11556](https://github.com/magento/magento2/pull/11556)) + * [#4004](https://github.com/magento/magento2/issues/4004) -- Newsletter Subscriber create-date not set, and change_status_at broken (fixed in [#11879](https://github.com/magento/magento2/pull/11879)) + * [#7225](https://github.com/magento/magento2/issues/7225) -- [BUG] [Magento 2.1.2] Programmatically creating an empty dropdown attribute, "apply_to" is set to NULL (from "simple") after adding options through store admin (fixed in [#11588](https://github.com/magento/magento2/pull/11588)) + * [#11197](https://github.com/magento/magento2/issues/11197) -- Blank page at the checkout 'shipping' step (fixed in [#11958](https://github.com/magento/magento2/pull/11958)) + * [#11880](https://github.com/magento/magento2/issues/11880) -- Magento 2.1.9 Configurable::getUsedProducts returns a different array after product collections is cached (fixed in [#12107](https://github.com/magento/magento2/pull/12107)) + * [#10811](https://github.com/magento/magento2/issues/10811) -- Replace FollowSymLinks with SymLinksIfOwnerMatch (fixed in [#11461](https://github.com/magento/magento2/pull/11461)) + * [#10920](https://github.com/magento/magento2/issues/10920) -- Sku => Entity_id relations are fetched inefficiently when inserting attributes values during product import (fixed in [#11719](https://github.com/magento/magento2/pull/11719)) + * [#6802](https://github.com/magento/magento2/issues/6802) -- Magento\Search\Helper\getSuggestUrl() not used in search template (fixed in [#11722](https://github.com/magento/magento2/pull/11722)) + * [#9151](https://github.com/magento/magento2/issues/9151) -- Sitemap.xml: lastmod timestamp can contain invalid dates (fixed in [#11902](https://github.com/magento/magento2/pull/11902)) + * [#10195](https://github.com/magento/magento2/issues/10195) -- Order relation child is not set during edit operation. (fixed in [#11988](https://github.com/magento/magento2/pull/11988)) + * [#11793](https://github.com/magento/magento2/issues/11793) -- Magento2.1.5 admin shipping report shows wrong currency code (fixed in [#11962](https://github.com/magento/magento2/pull/11962)) + * [#6661](https://github.com/magento/magento2/issues/6661) -- XHTML templates Don't Use Schema URNs (fixed in [#12031](https://github.com/magento/magento2/pull/12031)) + * [#12079](https://github.com/magento/magento2/issues/12079) -- Products in cart report error when we have grouped or bundle product (fixed in [#12082](https://github.com/magento/magento2/pull/12082)) + * [#9768](https://github.com/magento/magento2/issues/9768) -- Admin dashboard Most Viewed Products Tab only gives default attribute set's products (fixed in [#12139](https://github.com/magento/magento2/pull/12139)) + * [#6238](https://github.com/magento/magento2/issues/6238) -- Meta description allows too many characters (fixed in [#11914](https://github.com/magento/magento2/pull/11914)) + * [#11230](https://github.com/magento/magento2/issues/11230) -- Unit test fails after fresh installation (fixed in [#12144](https://github.com/magento/magento2/pull/12144)) + * [#10810](https://github.com/magento/magento2/issues/10810) -- Add support of apache2.4 commands in htaccess (fixed in [#11459](https://github.com/magento/magento2/pull/11459)) + * [#10834](https://github.com/magento/magento2/issues/10834) -- signing in after selecting checkout button, will not end up to checkout page! (fixed in [#11876](https://github.com/magento/magento2/pull/11876)) + * [#10477](https://github.com/magento/magento2/issues/10477) -- Cart price rule has failed if use dropdown attribute (fixed in [#11274](https://github.com/magento/magento2/pull/11274)) + * [#11832](https://github.com/magento/magento2/issues/11832) -- Create order (on Customer edit page) - not working from admin environment (fixed in [#11952](https://github.com/magento/magento2/pull/11952)) + * [#10014](https://github.com/magento/magento2/issues/10014) -- Newsletter subscriptions status not isolated between multi stores (fixed in [#12035](https://github.com/magento/magento2/pull/12035)) + * [#11532](https://github.com/magento/magento2/issues/11532) -- Duplicate Simple Product Throws Error: Undefined offset: 0 in SaveHandler.php on line 122 (fixed in [#12001](https://github.com/magento/magento2/pull/12001)) + * [#10628](https://github.com/magento/magento2/issues/10628) -- Color attribute swatches are not visible if sorting is enabled (fixed in [#12077](https://github.com/magento/magento2/pull/12077)) + * [#8022](https://github.com/magento/magento2/issues/8022) -- Uncaught Error: Call to a member function addItem() on array in app/code/Magento/Sales/Model/Order/Shipment.php (fixed in [#12173](https://github.com/magento/magento2/pull/12173)) +* GitHub pull requests: + * [#11240](https://github.com/magento/magento2/pull/11240) -- Virtual Theme load: Check for null to actually reach the code that handles this case to t… (by @leptoquark1) + * [#11261](https://github.com/magento/magento2/pull/11261) -- Prevent invoice cancelation multiple times 2.2-develop [Backport] (by @osrecio) + * [#11342](https://github.com/magento/magento2/pull/11342) -- ADDED $sortByPostion flag to getChildren() (by @denisristic) + * [#11351](https://github.com/magento/magento2/pull/11351) -- Fix the wrong input format of Customer date of birth (by @manuelson) + * [#11359](https://github.com/magento/magento2/pull/11359) -- [Backport 2.2-develop] Unable to manage (install/uninstall) cron via bin/magento cron:install / cron:remove with multiple installations against same crontab (by @adrian-martinez-interactiv4) + * [#11383](https://github.com/magento/magento2/pull/11383) -- Append shipment comment to shipment if appendComment is true (by @JeroenVanLeusden) + * [#11406](https://github.com/magento/magento2/pull/11406) -- Added carrier code to ID to distinguish shipping methods (by @peterjaap) + * [#11430](https://github.com/magento/magento2/pull/11430) -- Fix toolbar-amount placing in mobile device (by @slackerzz) + * [#11445](https://github.com/magento/magento2/pull/11445) -- Show product alerts in admin product detail [backport 2.2] (by @raumatbel) + * [#11470](https://github.com/magento/magento2/pull/11470) -- FR#10231_22 Custom URL Rewrite Not working [backport 2.2] (by @mrodespin) + * [#11493](https://github.com/magento/magento2/pull/11493) -- Add "optional" translation in checkout password field (by @JeroenVanLeusden) + * [#11199](https://github.com/magento/magento2/pull/11199) -- Add db-prefix from env conf when command admin:user:create is executed (by @osrecio) + * [#11299](https://github.com/magento/magento2/pull/11299) -- Update Guest.php (by @lano-vargas) + * [#11381](https://github.com/magento/magento2/pull/11381) -- Save region correctly to save sales address from admin (by @raumatbel) + * [#11460](https://github.com/magento/magento2/pull/11460) -- [ISSUE-11140][BUGFIX] Skip store code admin from being detected in ca… (by @diglin) + * [#11505](https://github.com/magento/magento2/pull/11505) -- [Backport-2.2] Retain additional cron history by default (by @mpchadwick) + * [#11437](https://github.com/magento/magento2/pull/11437) -- Add `confirmation` and `lock_expires ` to customer export csv - Fix issue 10765 (by @convenient) + * [#11486](https://github.com/magento/magento2/pull/11486) -- [Backport 2.2]Add VAT number to email source variables (by @JeroenVanLeusden) + * [#11495](https://github.com/magento/magento2/pull/11495) -- MAGETWO-75743: Fix for #9783 Multiple parameters in widget.… (by @diazwatson) + * [#11500](https://github.com/magento/magento2/pull/11500) -- MAGETWO-81245: Handling all setProductsFilter items in array as arguments (by @kirmorozov) + * [#11555](https://github.com/magento/magento2/pull/11555) -- Travis CI functional tests maintenance for 2.2-develop (by @ishakhsuvarov) + * [#11235](https://github.com/magento/magento2/pull/11235) -- [2.2-develop] Add static test to detect blocks without name attribute (by @ihor-sviziev) + * [#11569](https://github.com/magento/magento2/pull/11569) -- Fixed double space typo (by @dverkade) + * [#11565](https://github.com/magento/magento2/pull/11565) -- Fix "pattern" UI Component validation (by @bap14) + * [#11317](https://github.com/magento/magento2/pull/11317) -- [Backport 2.2-develop] Send email to subscribers only when are new (by @osrecio) + * [#11385](https://github.com/magento/magento2/pull/11385) -- Fix #10856: Sync billing with shipping address on Admin Order Page (by @joni-jones) + * [#11499](https://github.com/magento/magento2/pull/11499) -- Ensure database is cleared/Magento reinstalled when TESTS_CLEANUP is enabled (by @joshuaswarren) + * [#11510](https://github.com/magento/magento2/pull/11510) -- Add interaction to admin:user:create command (by @cmuench) + * [#11522](https://github.com/magento/magento2/pull/11522) -- [Backport 2.2-develop] Fix Filter Customer Report Review (by @osrecio) + * [#11553](https://github.com/magento/magento2/pull/11553) -- [2.2 Backport] ProductRepository sku cache is corrupted when cacheLimit is reached (by @heldchen) + * [#11561](https://github.com/magento/magento2/pull/11561) -- Magento sets ISO invalid language code (by @crissanclick) + * [#11591](https://github.com/magento/magento2/pull/11591) -- [Backport 2.2-develop] #11586 Fix duplicated crontab 2>&1 expression (by @adrian-martinez-interactiv4) + * [#11439](https://github.com/magento/magento2/pull/11439) -- [Backport 2.2-develop] #11328 : app:config:dump adds extra space every time in multiline array value (by @adrian-martinez-interactiv4) + * [#11675](https://github.com/magento/magento2/pull/11675) -- MAGETWO-77672: in system.xml translate phrase not work, if comment starts from new line. (by @nmalevanec) + * [#11673](https://github.com/magento/magento2/pull/11673) -- [BACKPORT 2.2] [TASK] Removed Typo in Paypal TestCase didgit => digit (by @lewisvoncken) + * [#11704](https://github.com/magento/magento2/pull/11704) -- [Backport 2.2-develop] Travis: surround variable TRAVIS_BRANCH with double-quotes instead of single-quotes (by @adrian-martinez-interactiv4) + * [#11677](https://github.com/magento/magento2/pull/11677) -- [BACKPORT 2.2] [TASK] Moved Customer Groups Menu Item from Other sett… (by @lewisvoncken) + * [#11676](https://github.com/magento/magento2/pull/11676) -- #7915: customer objects are equal to eachother after observing event customer_save_after_data_object (by @RomaKis) + * [#11250](https://github.com/magento/magento2/pull/11250) -- Fixing #10275 keyboard submit of adminhtml suggest form. (by @romainruaud) + * [#11421](https://github.com/magento/magento2/pull/11421) -- FIX #11022 in 2.2-develop: Filter Groups of search criteria parameter have not been included for further processing (by @davidverholen) + * [#11440](https://github.com/magento/magento2/pull/11440) -- Add missing translations in Magento_UI (by @JeroenVanLeusden) + * [#11643](https://github.com/magento/magento2/pull/11643) -- Fixed ability to set field config from layout xml #11302 [backport 2.2] (by @vovayatsyuk) + * [#11637](https://github.com/magento/magento2/pull/11637) -- MAGETWO-81311: Check the length of the array before attempting to sli… (by @briscoda) + * [#11635](https://github.com/magento/magento2/pull/11635) -- Coupon codes not showing in invoice (by @crissanclick) + * [#11690](https://github.com/magento/magento2/pull/11690) -- Add a health check to the NGINX configuration sample (by @andrewhowdencom) + * [#11710](https://github.com/magento/magento2/pull/11710) -- Allow coupon code with special charater to be applied to order in checkout (by @gabrielqs-redstage) + * [#11720](https://github.com/magento/magento2/pull/11720) -- Fix Notice: freePackageValue is undefined (by @amenk) + * [#11734](https://github.com/magento/magento2/pull/11734) -- [TASK] Updated user.ini according to Magento DevDocs (by @lewisvoncken) + * [#11751](https://github.com/magento/magento2/pull/11751) -- [Backport 2.2-develop] Dashboard Fix Y Axis for range (by @osrecio) + * [#11749](https://github.com/magento/magento2/pull/11749) -- [Backport 2.2-develop] Fix datetime type product that show current date when is empty in grids (by @enriquei4) + * [#11745](https://github.com/magento/magento2/pull/11745) -- [Backport 2.2-develop] Fix label to avoid wrapping poorly,now break by word (by @enriquei4) + * [#11765](https://github.com/magento/magento2/pull/11765) -- Allows modules with underscores in name to add blocks to layout via XML (by @bentideswell) + * [#11410](https://github.com/magento/magento2/pull/11410) -- "Ignore this notification" isn't working (by @crissanclick) + * [#11607](https://github.com/magento/magento2/pull/11607) -- [Backport 2.2-develop] Fix AcountManagementTest unit test fail randomly (by @adrian-martinez-interactiv4) + * [#11610](https://github.com/magento/magento2/pull/11610) -- FR#6891_22 Add-to-cart checkbox still visible when = false (by @mrodespin) + * [#11757](https://github.com/magento/magento2/pull/11757) -- Fix #11729 - negative value in excel export [M2.2] (by @hauso) + * [#11363](https://github.com/magento/magento2/pull/11363) -- Issue #6924: Unmask exception message during product import (by @tim-bezhashvyly) + * [#11425](https://github.com/magento/magento2/pull/11425) -- Magento setup:install interactive shell (by @denisristic) + * [#11767](https://github.com/magento/magento2/pull/11767) -- 7640: X-Magento-Tags header containing whitespaces causes exception (by @nmalevanec) + * [#11779](https://github.com/magento/magento2/pull/11779) -- MAGETWO-4711: Improve error reporting for products images import. (by @p-bystritsky) + * [#11830](https://github.com/magento/magento2/pull/11830) -- Fix #11581: Reference to wrong / non-existing class (by @dverkade) + * [#11827](https://github.com/magento/magento2/pull/11827) -- Admin product search - Pressing enter does not submit #4696 (by @bohemiorulo) + * [#11337](https://github.com/magento/magento2/pull/11337) -- #11211 Fix Store View switcher (by @thiagolima-bm) + * [#11458](https://github.com/magento/magento2/pull/11458) -- Products added to cart with REST API give total prices equal to zero (by @peterjaap) + * [#11595](https://github.com/magento/magento2/pull/11595) -- Fix issue #10032 - Download back-up .tgz always takes the latest that's created (2.2-develop) (by @PieterCappelle) + * [#11747](https://github.com/magento/magento2/pull/11747) -- [Backport 2.2-develop] FIX show visual swatches in admin - product attribute (by @enriquei4) + * [#11824](https://github.com/magento/magento2/pull/11824) -- Magetwo 70954: Remove the component.clear from the custom options type. This causes the 'elem' array to become out of sync with the recordData (by @briscoda) + * [#11651](https://github.com/magento/magento2/pull/11651) -- [BUGFIX] Solved error while upgrading from 2.1 to 2.2 (by @lewisvoncken) + * [#11802](https://github.com/magento/magento2/pull/11802) -- #8236 FIX CMS blocks (by @thiagolima-bm) + * [#11843](https://github.com/magento/magento2/pull/11843) -- Save the price 0 as price in custom options [backport 2.2] (by @raumatbel) + * [#11854](https://github.com/magento/magento2/pull/11854) -- FilterBuilder Doc Block Update (by @ByteCreation) + * [#11397](https://github.com/magento/magento2/pull/11397) -- Fix for #9566: Show the correct label in the admin (by @michielgerritsen) + * [#11732](https://github.com/magento/magento2/pull/11732) -- MAGETWO-5015: Report error csv doesn't work when trying to import a csv file with semicolon delimiter. (by @p-bystritsky) + * [#11829](https://github.com/magento/magento2/pull/11829) -- Fix #10682: Meta description and keywords transform to html entities (by @dverkade) + * [#11933](https://github.com/magento/magento2/pull/11933) -- Changed constructor typo in Javascript class (by @dverkade) + * [#11911](https://github.com/magento/magento2/pull/11911) -- Order grid - Sort by Purchase Date Desc by default (by @ihor-sviziev) + * [#11817](https://github.com/magento/magento2/pull/11817) -- GITHUB-8970: Cannot assign products to categories not under tree root. (by @p-bystritsky) + * [#11405](https://github.com/magento/magento2/pull/11405) -- Allow setting of http response status code in a Redirection (by @gabrielqs-redstage) + * [#11858](https://github.com/magento/magento2/pull/11858) -- #11697 Theme: Added html node to page xml root, cause validation error (by @adrian-martinez-interactiv4) + * [#11869](https://github.com/magento/magento2/pull/11869) -- Resolve Error While Trying To Load Quote Item Collection Using Magent… (by @neeta-wagento) + * [#11889](https://github.com/magento/magento2/pull/11889) -- Save background color correctly in images. [backport 2.2] (by @raumatbel) + * [#11917](https://github.com/magento/magento2/pull/11917) -- [BACKPORT 2.2] [TASK] Add resetPassword call to the webapi (by @lewisvoncken) + * [#11949](https://github.com/magento/magento2/pull/11949) -- 11868: "Add Products" button has been duplicated after the customer group was changed. (by @nmalevanec) + * [#11959](https://github.com/magento/magento2/pull/11959) -- #11898 - Change NL PostCode Pattern (by @osrecio) + * [#11620](https://github.com/magento/magento2/pull/11620) -- Check attribute unique between same fields in magento commerce (by @raumatbel) + * [#11770](https://github.com/magento/magento2/pull/11770) -- Product attribute creation page handles Storefront tab visibility wrong (by @euronetzrt) + * [#11863](https://github.com/magento/magento2/pull/11863) -- Update wrong layout update xml handle installed in CMS Home Page by default (by @adrian-martinez-interactiv4) + * [#12011](https://github.com/magento/magento2/pull/12011) -- [backport 2.2] Magento 2 Store Code validation regex: doesn't support uppercase letters in store code (by @manuelson) + * [#12013](https://github.com/magento/magento2/pull/12013) -- Add validation for number of street lines (by @crissanclick) + * [#11785](https://github.com/magento/magento2/pull/11785) -- fix #8846: avoid duplicated attribute option values (by @gomencal) + * [#11993](https://github.com/magento/magento2/pull/11993) -- 11700: "Something Went Wrong" error for limited access admin user (by @RomaKis) + * [#12018](https://github.com/magento/magento2/pull/12018) -- Magento 2.2.0 Solution for Cross-sell product placeholder image size … (by @emiprotech) + * [#11556](https://github.com/magento/magento2/pull/11556) -- Fix #10583: Checkout place order exception when using a new address (by @joni-jones) + * [#11879](https://github.com/magento/magento2/pull/11879) -- #4004: Newsletter Subscriber create-date not set, and change_status_at broken (by @nemesis-back) + * [#11588](https://github.com/magento/magento2/pull/11588) -- Fix Issue #7225 - Remove hardcoding of apply_to when saving attributes (by @MartinPeverelli) + * [#11958](https://github.com/magento/magento2/pull/11958) -- 11197: Blank page at the checkout 'shipping' step[backport]. (by @nmalevanec) + * [#12091](https://github.com/magento/magento2/pull/12091) -- Fix "Undefined variable: responseAjax" notice when trying to save a shipment package (by @lazyguru) + * [#11461](https://github.com/magento/magento2/pull/11461) -- [ISSUE-10811][BUGFIX] Update .htaccess.sample to replace FollowSymLin… (by @diglin) + * [#11719](https://github.com/magento/magento2/pull/11719) -- 10920: Sku => Entity_id relations are fetched inefficiently when inserting attributes values during product import. (by @nmalevanec) + * [#11722](https://github.com/magento/magento2/pull/11722) -- 6802: Magento\Search\Helper\getSuggestUrl() not used in search template. (by @nmalevanec) + * [#11857](https://github.com/magento/magento2/pull/11857) -- CMS Page - CMS Page - Force validate layout update xml in production mode when saving CMS Page - Handle layout update xml validation exceptions (by @adrian-martinez-interactiv4) + * [#11902](https://github.com/magento/magento2/pull/11902) -- #9151: [Github] Sitemap.xml: lastmod timestamp can contain invalid dates (by @serhii-balko) + * [#11947](https://github.com/magento/magento2/pull/11947) -- Fix json encoded attribute backend type when attribute value is null (by @tkotosz) + * [#11962](https://github.com/magento/magento2/pull/11962) -- 11793: Magento2.1.5 admin shipping report shows wrong currency code (by @RomaKis) + * [#11988](https://github.com/magento/magento2/pull/11988) -- 10195: Order relation child is not set during edit operation(backport from 2.3 to 2.2) (by @RomaKis) + * [#12031](https://github.com/magento/magento2/pull/12031) -- Improve urn in xhtml (by @enriquei4) + * [#12082](https://github.com/magento/magento2/pull/12082) -- Products in cart report error when we have grouped or bundle product (by @mihaifaget) + * [#12131](https://github.com/magento/magento2/pull/12131) -- [Backport 2.2] Close PayPal popup window in case of rejected request #10820 (by @vovayatsyuk) + * [#12139](https://github.com/magento/magento2/pull/12139) -- 9768: Admin dashboard Most Viewed Products Tab only gives default attribute set's products(backport for 2.2) (by @RomaKis) + * [#11914](https://github.com/magento/magento2/pull/11914) -- [BACKPORT 2.2] [BUGFIX] All UI input fields should have maxlength of 255 because of V… (by @lewisvoncken) + * [#11944](https://github.com/magento/magento2/pull/11944) -- Report Handled Exceptions To New Relic (by @mpchadwick) + * [#12144](https://github.com/magento/magento2/pull/12144) -- Removed FileClassScannerTest dependency to "Magento_Catalog" (by @wexo-team) + * [#11459](https://github.com/magento/magento2/pull/11459) -- close #10810 Migrates Apache Access Syntax to 2.4 on Apache >= 2.4 (by @jonashrem) + * [#11968](https://github.com/magento/magento2/pull/11968) -- Fix bug: Customer import deletes exiting customer entity Fields (by @jalogut) + * [#12061](https://github.com/magento/magento2/pull/12061) -- Cleanup for object manager references and depricated method (by @atishgoswami) + * [#12136](https://github.com/magento/magento2/pull/12136) -- update button.phtml overcomplicated translation phrase. 2.2 (by @ChuckyK) + * [#11876](https://github.com/magento/magento2/pull/11876) -- After logging in customer is now not redirecting to Customer Dashboard by default (by @p-bystritsky) + * [#11274](https://github.com/magento/magento2/pull/11274) -- Fix #10477 Check cart rule subselect conditions against quote item children too (by @marinagociu) + * [#11952](https://github.com/magento/magento2/pull/11952) -- 11832: Create order (on Customer edit page) - not working from admin environment (by @RomaKis) + * [#12035](https://github.com/magento/magento2/pull/12035) -- Fix newsletter subscriptions between stores (by @sbaixauli) + * [#12001](https://github.com/magento/magento2/pull/12001) -- 11532: Duplicate Simple Product Throws Error: Undefined offset: 0 in SaveHandler.php on line 122 (by @RomaKis) + * [#12077](https://github.com/magento/magento2/pull/12077) -- 10628: Color attribute swatches are not visible if sorting is enabled (by @RomaKis) + * [#12130](https://github.com/magento/magento2/pull/12130) -- [Backport 2.2] MAGETWO-71697: Fix possible bug when saving address with empty street line #10582 (by @vovayatsyuk) + * [#12141](https://github.com/magento/magento2/pull/12141) -- Fix js error when disable/enable wysiwyg editor (by @vovayatsyuk) + * [#12173](https://github.com/magento/magento2/pull/12173) -- 8022: Uncaught Error: Call to a member function addItem() on array in app/code/Magento/Sales/Model/Order/Shipment.php(backport to 2.2) (by @RomaKis) + +2.2.1 +============= +* GitHub issues: + * [#4248](https://github.com/magento/magento2/issues/4248) -- Validations not working on customer registration on DOB field. (fixed in [#11067](https://github.com/magento/magento2/pull/11067)) + * [#6350](https://github.com/magento/magento2/issues/6350) -- Frontend: Datepicker/calendar control does not use the store locale (fixed in [#11067](https://github.com/magento/magento2/pull/11067)) + * [#6858](https://github.com/magento/magento2/issues/6858) -- DatePicker date format does not reflect user's locale (fixed in [#11067](https://github.com/magento/magento2/pull/11067)) + * [#6831](https://github.com/magento/magento2/issues/6831) -- Magento 2.1.1 Invalid input date format 'Invalid date' (fixed in [#11067](https://github.com/magento/magento2/pull/11067)) + * [#9743](https://github.com/magento/magento2/issues/9743) -- Invalid date when customer validate with French locale (fixed in [#11067](https://github.com/magento/magento2/pull/11067)) + * [#6712](https://github.com/magento/magento2/issues/6712) -- Footer Links Widget CSS Issue (fixed in [#11063](https://github.com/magento/magento2/pull/11063)) + * [#9008](https://github.com/magento/magento2/issues/9008) -- Error Message Is Confusing When Code Base Is Behind Database Module Version (fixed in [#11064](https://github.com/magento/magento2/pull/11064)) + * [#9981](https://github.com/magento/magento2/issues/9981) -- M2 suggests running setup:upgrade if version number of module is higher than expected (fixed in [#11064](https://github.com/magento/magento2/pull/11064)) + * [#10824](https://github.com/magento/magento2/issues/10824) -- Cannot add new columns to item grid in admin sales_order_view layout (fixed in [#11076](https://github.com/magento/magento2/pull/11076)) + * [#10417](https://github.com/magento/magento2/issues/10417) -- Wysywig editor shows broken image icons (fixed in [#11048](https://github.com/magento/magento2/pull/11048)) + * [#10697](https://github.com/magento/magento2/issues/10697) -- Product Import: Additional data: Invalid URL key (fixed in [#11049](https://github.com/magento/magento2/pull/11049)) + * [#10474](https://github.com/magento/magento2/issues/10474) -- Error message in product review form not being translated (fixed in [#11069](https://github.com/magento/magento2/pull/11069)) + * [#9877](https://github.com/magento/magento2/issues/9877) -- getCacheTags for price issue (fixed in [#11154](https://github.com/magento/magento2/pull/11154)) + * [#10738](https://github.com/magento/magento2/issues/10738) -- Empty attribute label is displayed on product page when other language used. (fixed in [#11168](https://github.com/magento/magento2/pull/11168)) + * [#9900](https://github.com/magento/magento2/issues/9900) -- Cms module collections missing event prefix (fixed in [#11223](https://github.com/magento/magento2/pull/11223)) + * [#11044](https://github.com/magento/magento2/issues/11044) -- magento setup:upgrade prompts to run compilation, even in developer mode (fixed in [#11050](https://github.com/magento/magento2/pull/11050)) + * [#10775](https://github.com/magento/magento2/issues/10775) -- 404 Forbidden sounds not right (fixed in [#11134](https://github.com/magento/magento2/pull/11134)) + * [#11231](https://github.com/magento/magento2/issues/11231) -- Can't close mobile search bar once typed (fixed in [#11246](https://github.com/magento/magento2/pull/11246)) + * [#10317](https://github.com/magento/magento2/issues/10317) -- Region is being overridden when changing from a required-state country to one that is not required (fixed in [#11254](https://github.com/magento/magento2/pull/11254)) + * [#11089](https://github.com/magento/magento2/issues/11089) -- setup:config:set --key append instead of replace (fixed in [#11155](https://github.com/magento/magento2/pull/11155)) + * [#7582](https://github.com/magento/magento2/issues/7582) -- Payment methods in payments title in wrong language (fixed in [#11165](https://github.com/magento/magento2/pull/11165)) + * [#5105](https://github.com/magento/magento2/issues/5105) -- Error While send Invoice with Grouped Products (fixed in [#11297](https://github.com/magento/magento2/pull/11297)) + * [#11163](https://github.com/magento/magento2/issues/11163) -- Magento 2.2.0 Pages showing error: Data key is missing: code-entity (fixed in [#11205](https://github.com/magento/magento2/pull/11205)) + * [#11329](https://github.com/magento/magento2/issues/11329) -- Unable to proceed massaction "Update attributes" with required multiple select attribute (fixed in [#11349](https://github.com/magento/magento2/pull/11349)) + * [#8958](https://github.com/magento/magento2/issues/8958) -- Hint mistake in english language (fixed in [#11390](https://github.com/magento/magento2/pull/11390)) +* GitHub pull requests: + * [#11067](https://github.com/magento/magento2/pull/11067) -- Fix dob date validation on custom locale (by @joachimVT) + * [#11054](https://github.com/magento/magento2/pull/11054) -- Add dev:tests:run parameter to pass arguments to phpunit (by @schmengler) + * [#11056](https://github.com/magento/magento2/pull/11056) -- Do not disable maintenance mode after running a backup. (by @stevenvdp) + * [#11058](https://github.com/magento/magento2/pull/11058) -- Escape html before replace new line with break (by @Quinten) + * [#11063](https://github.com/magento/magento2/pull/11063) -- 6712 Remove additional margin for footer links widget; prevents layou… (by @fragdochkarl) + * [#11064](https://github.com/magento/magento2/pull/11064) -- Show different message if DB module version is higher than code modul… (by @schmengler) + * [#11076](https://github.com/magento/magento2/pull/11076) -- Backport to 2.2 of #10824: add name for order items grid default renderer block (by @gsomoza) + * [#11048](https://github.com/magento/magento2/pull/11048) -- Fix #10417 (by @PieterCappelle) + * [#11049](https://github.com/magento/magento2/pull/11049) -- Vague error message for invalid url_key for category (by @avdb) + * [#11069](https://github.com/magento/magento2/pull/11069) -- Error message in product review form not being translated (by @Echron) + * [#11127](https://github.com/magento/magento2/pull/11127) -- Add missing return statements in setters (by @niccifor) + * [#11138](https://github.com/magento/magento2/pull/11138) -- Added template as argument to the store address renderer to allow custom formatting (by @jokeputs) + * [#11147](https://github.com/magento/magento2/pull/11147) -- Fix the correct removal of the images and the removal of all images in the catalog (by @raumatbel) + * [#11154](https://github.com/magento/magento2/pull/11154) -- Issue #9877: Backport of: getCacheTags for price issue #10930 (by @denysbabenko) + * [#11160](https://github.com/magento/magento2/pull/11160) -- Ported Down changes from PR#10919 (by @strell) + * [#11200](https://github.com/magento/magento2/pull/11200) -- Delete CallExit function for After plugin logic execution 2.2-develop [BackPort] (by @osrecio) + * [#11168](https://github.com/magento/magento2/pull/11168) -- Fixed issue #10738: Don't display attribute label if defined as "none" in layout (by @maksek) + * [#11223](https://github.com/magento/magento2/pull/11223) -- Cms page/block eventprefix fix issue 9900 (by @convenient) + * [#11229](https://github.com/magento/magento2/pull/11229) -- Disable secret key validation on admin noroute to fix redirect loop (by @convenient) + * [#11050](https://github.com/magento/magento2/pull/11050) -- Only prompt for deploy command in production mode (by @schmengler) + * [#11134](https://github.com/magento/magento2/pull/11134) -- Fix 404 status header (by @Zifius) + * [#11084](https://github.com/magento/magento2/pull/11084) -- Fix in stripped min length validation when value has special characters (by @rubenRP) + * [#11246](https://github.com/magento/magento2/pull/11246) -- Can't close mobile search bar (by @crissanclick) + * [#11254](https://github.com/magento/magento2/pull/11254) -- Fix for #10317 Disable region list when switching to a region optional country. (by @romainruaud) + * [#11155](https://github.com/magento/magento2/pull/11155) -- Refactor ConfigGenerator to replace/set crypt key instead of append (by @renttek) + * [#11291](https://github.com/magento/magento2/pull/11291) -- Fix #9243 - Upgrade ZF components. Zend_Service (by @dverkade) + * [#11165](https://github.com/magento/magento2/pull/11165) -- #7582: use setStoreId after custom load method to give storeId precedence (by @bka) + * [#11297](https://github.com/magento/magento2/pull/11297) -- Fix for issue #5105 - Error While send Invoice with Grouped Products (by @michielgerritsen) + * [#11327](https://github.com/magento/magento2/pull/11327) -- Fix #10812: htaccess Options override (by @dverkade) + * [#11081](https://github.com/magento/magento2/pull/11081) -- Text updated. (by @RakeshJesadiya) + * [#11183](https://github.com/magento/magento2/pull/11183) -- FIX for #11166 Index Handling Fatal Error (by @larsroettig) + * [#11205](https://github.com/magento/magento2/pull/11205) -- Fixes #11163 missing data key code-entity in CMS page edit form (by @Tomasz-Silpion) + * [#11219](https://github.com/magento/magento2/pull/11219) -- Fix typo in sessionStorage polyfill (by @mszydlo) + * [#11249](https://github.com/magento/magento2/pull/11249) -- Add a payload extender to the default shipping-save-processor (Backport to 2.2) (by @navarr) + * [#11345](https://github.com/magento/magento2/pull/11345) -- Use US English spelling for "Optimization". (by @davidangel) + * [#11349](https://github.com/magento/magento2/pull/11349) -- Bug fix update attributes (by @manuelson) + * [#11390](https://github.com/magento/magento2/pull/11390) -- Fix typo in design rule hint message (by @jahvi) + +2.2.0 +============= +* GitHub issues: + * [#8287](https://github.com/magento/magento2/issues/8287) -- InputException while updating cart with Minimum order amount enabled (fixed in [#8474](https://github.com/magento/magento2/pull/8474)) + * [#8315](https://github.com/magento/magento2/issues/8315) -- Error with StdoTest (fixed in [#8487](https://github.com/magento/magento2/pull/8487)) + * [#5808](https://github.com/magento/magento2/issues/5808) -- [2.1.0] Problem on mobile when catalog gallery allowfullscreen is false (fixed in [#8434](https://github.com/magento/magento2/pull/8434)) + * [#8392](https://github.com/magento/magento2/issues/8392) -- Initial loading of the related-products sorting fails in Edge-Mode (fixed in [#8467](https://github.com/magento/magento2/pull/8467)) + * [#8076](https://github.com/magento/magento2/issues/8076) -- Currency Setup in admin throws in_array error when a single value is selected (fixed in [#8077](https://github.com/magento/magento2/pull/8077)) + * [#8277](https://github.com/magento/magento2/issues/8277) -- CatalogImportExport uploader can't handle HTTPS images (fixed in [#8278](https://github.com/magento/magento2/pull/8278)) + * [#7353](https://github.com/magento/magento2/issues/7353) -- Crosssells are never shown when using product/list/items.phtml template (fixed in [#8602](https://github.com/magento/magento2/pull/8602)) + * [#8632](https://github.com/magento/magento2/issues/8632) -- Location of wishlist.js file is incorrect (fixed in [#8633](https://github.com/magento/magento2/pull/8633)) + * [#6634](https://github.com/magento/magento2/issues/6634) -- Yes/No attribute value is not shown on a product details page (fixed in [#8623](https://github.com/magento/magento2/pull/8623)) + * [#8566](https://github.com/magento/magento2/issues/8566) -- Setting 'show_out_of_stock' to 'No' has no effect (fixed in [#8736](https://github.com/magento/magento2/pull/8736)) + * [#6706](https://github.com/magento/magento2/issues/6706) -- Notice undefined index when changes swatch attribute (fixed in [#6707](https://github.com/magento/magento2/pull/6707)) + * [#6855](https://github.com/magento/magento2/issues/6855) -- Create order via backend doesn't work after using NL translation for order-header (fixed in [#6856](https://github.com/magento/magento2/pull/6856)) + * [#8515](https://github.com/magento/magento2/issues/8515) -- Downloadable product is available for download even if order state is set canceled. (fixed in [#8917](https://github.com/magento/magento2/pull/8917)) + * [#8871](https://github.com/magento/magento2/issues/8871) -- Typo in Pull Request Template (fixed in [#8908](https://github.com/magento/magento2/pull/8908)) + * [#3791](https://github.com/magento/magento2/issues/3791) -- Error review when customer is not logged (fixed in [#9001](https://github.com/magento/magento2/pull/9001)) + * [#9017](https://github.com/magento/magento2/issues/9017) -- Duplicate call to $this->getLinkField(); (fixed in [#9057](https://github.com/magento/magento2/pull/9057)) + * [#7498](https://github.com/magento/magento2/issues/7498) -- Grammar Mistake: You don't subscribe to our newsletter. (fixed in [#9080](https://github.com/magento/magento2/pull/9080)) + * [#9078](https://github.com/magento/magento2/issues/9078) -- Does not translate "Track All Shipments" in My Account, view order (fixed in [#9095](https://github.com/magento/magento2/pull/9095)) + * [#5731](https://github.com/magento/magento2/issues/5731) -- FPT label not translatable in the totals on the cart page. (fixed in [#9204](https://github.com/magento/magento2/pull/9204)) + * [#5040](https://github.com/magento/magento2/issues/5040) -- Change 'select' to 'query' in AbstractSearchResult (fixed in [#5043](https://github.com/magento/magento2/pull/5043)) + * [#8761](https://github.com/magento/magento2/issues/8761) -- Popup-Modal not closing on Safari/Windows (fixed in [#8824](https://github.com/magento/magento2/pull/8824)) + * [#7549](https://github.com/magento/magento2/issues/7549) -- Google API Tracking code missing single quote after account number (fixed in [#9084](https://github.com/magento/magento2/pull/9084)) + * [#1146](https://github.com/magento/magento2/issues/1146) -- A few issues when you use /pub as DocumentRoot (fixed in [#9094](https://github.com/magento/magento2/pull/9094)) + * [#2802](https://github.com/magento/magento2/issues/2802) -- Sitemap generation in wrong folder when vhost is connected to pub folder (fixed in [#9094](https://github.com/magento/magento2/pull/9094)) + * [#9200](https://github.com/magento/magento2/issues/9200) -- Create new CLI command: Enable DB logging (fixed in [#9264](https://github.com/magento/magento2/pull/9264)) + * [#9199](https://github.com/magento/magento2/issues/9199) -- Create New CLI command: Generate Varnish VCL file (fixed in [#9286](https://github.com/magento/magento2/pull/9286)) + * [#6822](https://github.com/magento/magento2/issues/6822) -- CSS Minify Option Breaks inline SVG XML (fixed in [#9027](https://github.com/magento/magento2/pull/9027)) + * [#8552](https://github.com/magento/magento2/issues/8552) -- CSS Minifying not compatible with CSS3 calc() function (fixed in [#9027](https://github.com/magento/magento2/pull/9027)) + * [#9236](https://github.com/magento/magento2/issues/9236) -- Upgrade ZF components. Zend_Json (fixed in [#9262](https://github.com/magento/magento2/pull/9262) and [#9261](https://github.com/magento/magento2/pull/9261) and [#9344](https://github.com/magento/magento2/pull/9344) and [#9753](https://github.com/magento/magento2/pull/9753) and [#9754](https://github.com/magento/magento2/pull/9754)) + * [#7523](https://github.com/magento/magento2/issues/7523) -- Zend_Db_Statement_Exception after refreshing browser with empty category (fixed in [#9400](https://github.com/magento/magento2/pull/9400)) + * [#9237](https://github.com/magento/magento2/issues/9237) -- Upgrade ZF components. Zend_Log (fixed in [#9285](https://github.com/magento/magento2/pull/9285)) + * [#9518](https://github.com/magento/magento2/issues/9518) -- Chrome version 58 causes problems with selections in the tinymce editor (fixed in [#9540](https://github.com/magento/magento2/pull/9540)) + * [#9241](https://github.com/magento/magento2/issues/9241) -- Upgrade ZF components. Zend_Wildfire (fixed in [#9622](https://github.com/magento/magento2/pull/9622)) + * [#9239](https://github.com/magento/magento2/issues/9239) -- Upgrade ZF components. Zend_Controller (fixed in [#9622](https://github.com/magento/magento2/pull/9622)) + * [#6455](https://github.com/magento/magento2/issues/6455) -- Cookie Restriction Mode Overlay is cached by Varnish (fixed in [#9711](https://github.com/magento/magento2/pull/9711)) + * [#5596](https://github.com/magento/magento2/issues/5596) -- Google Universal Analytics does not track when Cookie Restriction is enabled (fixed in [#9713](https://github.com/magento/magento2/pull/9713)) + * [#6441](https://github.com/magento/magento2/issues/6441) -- Google Analytics Tracking Code cached by Varnish if Cookie Restriction Settings are active (fixed in [#9713](https://github.com/magento/magento2/pull/9713)) + * [#9242](https://github.com/magento/magento2/issues/9242) -- Upgrade ZF components. Zend_Session (fixed in [#9348](https://github.com/magento/magento2/pull/9348)) + * [#7279](https://github.com/magento/magento2/issues/7279) -- Bill-to Name and Ship-to Name trancated to 20 characters in backend (fixed in [#9654](https://github.com/magento/magento2/pull/9654)) + * [#6151](https://github.com/magento/magento2/issues/6151) -- Can't delete last item in cart if Minimum Order is Enable (fixed in [#9714](https://github.com/magento/magento2/pull/9714)) + * [#4272](https://github.com/magento/magento2/issues/4272) -- v2.0.4 Credit memos with adjustment fees cannot be fully refunded with a second credit memo (fixed in [#9715](https://github.com/magento/magento2/pull/9715)) + * [#6207](https://github.com/magento/magento2/issues/6207) -- Checkbox IDs for Terms and Conditions should be unique in Checkout (fixed in [#9717](https://github.com/magento/magento2/pull/9717)) + * [#7844](https://github.com/magento/magento2/issues/7844) -- Customer with unique attribute can't be saved (fixed in [#9712](https://github.com/magento/magento2/pull/9712)) + * [#6244](https://github.com/magento/magento2/issues/6244) -- Promo code label not used (fixed in [#9721](https://github.com/magento/magento2/pull/9721)) + * [#9771](https://github.com/magento/magento2/issues/9771) -- XML instruction referenceBlock does not allow template= or does it? (fixed in [#9772](https://github.com/magento/magento2/pull/9772)) + * [#8221](https://github.com/magento/magento2/issues/8221) -- Javascript "mixins" doesn't works if 'urlArgs' is in requirejs-config.js (fixed in [#9665](https://github.com/magento/magento2/pull/9665)) + * [#9278](https://github.com/magento/magento2/issues/9278) -- Create new CLI command: Enable Template Hints (fixed in [#9778](https://github.com/magento/magento2/pull/9778)) + * [#9819](https://github.com/magento/magento2/issues/9819) -- Authentication_lock settings cannot be edited in the Backend (fixed in [#9820](https://github.com/magento/magento2/pull/9820)) + * [#9216](https://github.com/magento/magento2/issues/9216) -- Coupon codes not showing in invoice print out (fixed in [#9780](https://github.com/magento/magento2/pull/9780)) + * [#9679](https://github.com/magento/magento2/issues/9679) -- Translation for layered navigation attribute option not working (fixed in [#9873](https://github.com/magento/magento2/pull/9873)) + * [#3060](https://github.com/magento/magento2/issues/3060) -- setup:static-content:deploy, setup:di:compile and deploy:mode:set will not return a non-zero exit code if any error occurs (fixed in [#7780](https://github.com/magento/magento2/pull/7780)) + * [#9421](https://github.com/magento/magento2/issues/9421) -- Inconsistent Gift Options checkbox labels (fixed in [#9525](https://github.com/magento/magento2/pull/9525)) + * [#9805](https://github.com/magento/magento2/issues/9805) -- Static tests in Windows fail due to file path mismatches (fixed in [#9902](https://github.com/magento/magento2/pull/9902)) + * [#9924](https://github.com/magento/magento2/issues/9924) -- Prefix and suffix are not prefilled in the quote shipping address (fixed in [#9925](https://github.com/magento/magento2/pull/9925)) + * [#4237](https://github.com/magento/magento2/issues/4237) -- Cron times in the database have a double timezone correction (fixed in [#9943](https://github.com/magento/magento2/pull/9943)) + * [#9426](https://github.com/magento/magento2/issues/9426) -- Incorrect order date in Orders grid (fixed in [#9941](https://github.com/magento/magento2/pull/9941)) + * [#3380](https://github.com/magento/magento2/issues/3380) -- Remove scheduled jobs after changing cron settings (fixed in [#9957](https://github.com/magento/magento2/pull/9957)) + * [#7504](https://github.com/magento/magento2/issues/7504) -- sitemap image URLs do not match with those on product pages (fixed in [#9082](https://github.com/magento/magento2/pull/9082)) + * [#8732](https://github.com/magento/magento2/issues/8732) -- The country drop-down list display incorrect after upgrade to 2.1.4 in Admin (fixed in [#9429](https://github.com/magento/magento2/pull/9429)) + * [#9680](https://github.com/magento/magento2/issues/9680) -- Adding product configurations uses sku based name (fixed in [#9681](https://github.com/magento/magento2/pull/9681)) + * [#10017](https://github.com/magento/magento2/issues/10017) -- Customer Backend: Confirmation Flag is being overwritten by save of the Customer (fixed in [#9681](https://github.com/magento/magento2/pull/9681)) + * [#2266](https://github.com/magento/magento2/issues/2266) -- Action column menu does not stay over the action column (fixed in [#10082](https://github.com/magento/magento2/pull/10082)) + * [#5381](https://github.com/magento/magento2/issues/5381) -- Cannot create a `etc/view.xml` file with an `images` tag with an attribute `module` other than `Magento_Catalog` (fixed in [#10052](https://github.com/magento/magento2/pull/10052)) + * [#6337](https://github.com/magento/magento2/issues/6337) -- Not able to translate page layout head titles (fixed in [#9992](https://github.com/magento/magento2/pull/9992)) + * [#3872](https://github.com/magento/magento2/issues/3872) -- Slash as category URL suffix gives 404 error on all category pages (fixed in [#10043](https://github.com/magento/magento2/pull/10043)) + * [#4660](https://github.com/magento/magento2/issues/4660) -- Multiple URLs causes duplicated content (fixed in [#10043](https://github.com/magento/magento2/pull/10043)) + * [#4876](https://github.com/magento/magento2/issues/4876) -- Product URL Suffix "/" results in 404 error (fixed in [#10043](https://github.com/magento/magento2/pull/10043)) + * [#8264](https://github.com/magento/magento2/issues/8264) -- Custom URL Rewrite where the request path ends with a forward slash is not matched (fixed in [#10043](https://github.com/magento/magento2/pull/10043)) + * [#6396](https://github.com/magento/magento2/issues/6396) -- Cart Price Rules: Category selection UI for Conditions do not come up. (fixed in [#10094](https://github.com/magento/magento2/pull/10094)) + * [#10124](https://github.com/magento/magento2/issues/10124) -- Wrong order of "width" x "height" when uploading image to admin under Content>Design Config (fixed in [#10126](https://github.com/magento/magento2/pull/10126)) + * [#6594](https://github.com/magento/magento2/issues/6594) -- Magento 2.1 EE: simplexml_load_string() error in custom widget (fixed in [#10151](https://github.com/magento/magento2/pull/10151)) + * [#10148](https://github.com/magento/magento2/issues/10148) -- Developer ACL incorrect (fixed in [#10149](https://github.com/magento/magento2/pull/10149)) + * [#9445](https://github.com/magento/magento2/issues/9445) -- Cart 860 does not contain item 1204 (fixed in [#10059](https://github.com/magento/magento2/pull/10059)) +* GitHub pull requests: + * [#4078](https://github.com/magento/magento2/pull/4078) -- Add logo folder to list of allowed resources (by @thaiphan) + * [#4355](https://github.com/magento/magento2/pull/4355) -- Enforce password and password-confirm as strings (by @nevvermind) + * [#4175](https://github.com/magento/magento2/pull/4175) -- PHP 7 (by @rafaelstz) + * [#4669](https://github.com/magento/magento2/pull/4669) -- Php 5.5 support was removed (by @fooman) + * [#4766](https://github.com/magento/magento2/pull/4766) -- The time has come for splat operator (by @orlangur) + * [#4867](https://github.com/magento/magento2/pull/4867) -- Fix commandExecutor interface mismatch (by @wexo-team) + * [#3705](https://github.com/magento/magento2/pull/3705) -- fix issue #3704 regarding integer attribute values being cast to decimal (by @digitalpianism) + * [#3750](https://github.com/magento/magento2/pull/3750) -- Add ability to show custom error message on Authorizenet place order (by @ytorbyk) + * [#3859](https://github.com/magento/magento2/pull/3859) -- Fixes _toHtml method of Checkout/Block/Cart/Link class (by @ddonnini) + * [#4017](https://github.com/magento/magento2/pull/4017) -- Add the $t to translate the message (by @mamzellejuu) + * [#4239](https://github.com/magento/magento2/pull/4239) -- Attribute Model specifiable in Propertymapper (by @liolemaire) + * [#4583](https://github.com/magento/magento2/pull/4583) -- Fix desktop spelling in lib/web/css #4557 (by @BenSpace48) + * [#2735](https://github.com/magento/magento2/pull/2735) -- Add database port to Magento Setup Model Installer (by @tkn98) + * [#4275](https://github.com/magento/magento2/pull/4275) -- Database port missing in Magento\TestFramework\Db\Mysql #3529 (by @gordonknoppe) + * [#4372](https://github.com/magento/magento2/pull/4372) -- Remove unused property + use ::class (by @nevvermind) + * [#2274](https://github.com/magento/magento2/pull/2274) -- TypeError: this.trigger is not a function (by @daim2k5) + * [#3050](https://github.com/magento/magento2/pull/3050) -- Replace PrototypeJS (by @srenon) + * [#3436](https://github.com/magento/magento2/pull/3436) -- Fix stripped-min-length check (by @avoelkl) + * [#3638](https://github.com/magento/magento2/pull/3638) -- Fix #3637 Add missing catalogsearch layout for swatches. (by @romainruaud) + * [#3633](https://github.com/magento/magento2/pull/3633) -- #768 - fix Missing acl.xml for the Magento_Checkout module (by @tzyganu) + * [#3708](https://github.com/magento/magento2/pull/3708) -- Add 'yyyy' to nomalizedDate method map for adminhtml i18n (by @Amakata) + * [#3973](https://github.com/magento/magento2/pull/3973) -- fix bug on newsletter subscription action when user use an existing email already subscribed (by @manfrinm) + * [#3932](https://github.com/magento/magento2/pull/3932) -- Update Date.php (by @Maddesto) + * [#3907](https://github.com/magento/magento2/pull/3907) -- Update Layout.php (by @Maddesto) + * [#4051](https://github.com/magento/magento2/pull/4051) -- Fix "minimum-length" option of the "validate-length" JS validation (by @adragus-inviqa) + * [#4269](https://github.com/magento/magento2/pull/4269) -- ConfigurableProduct validator, first check if array item exists (by @BlackIkeEagle) + * [#4496](https://github.com/magento/magento2/pull/4496) -- Access element through jQuery (by @sikker) + * [#4631](https://github.com/magento/magento2/pull/4631) -- Previous is spelt incorrectly (by @BenSpace48) + * [#4882](https://github.com/magento/magento2/pull/4882) -- Fix handling is_region_required key as optional (by @komsitr) + * [#5028](https://github.com/magento/magento2/pull/5028) -- Load jquery using requirejs to print page (by @Bartlomiejsz) + * [#1957](https://github.com/magento/magento2/pull/1957) -- Add required interface implementation (by @udovicic) + * [#2492](https://github.com/magento/magento2/pull/2492) -- No translation from "Orders and Returns" footer links (by @mageho) + * [#3749](https://github.com/magento/magento2/pull/3749) -- Stop screen loader on payment error (by @ytorbyk) + * [#4901](https://github.com/magento/magento2/pull/4901) -- Fix XML validation value type of post-code to "boolean" (by @adragus-inviqa) + * [#5066](https://github.com/magento/magento2/pull/5066) -- Add underscore as dependency to quote.js (by @Bartlomiejsz) + * [#5116](https://github.com/magento/magento2/pull/5116) -- Fixes invalid json objects in the order email templates. (by @hostep) + * [#5095](https://github.com/magento/magento2/pull/5095) -- Fix integration test expectation to match count of countries in the database (by @Vinai) + * [#5200](https://github.com/magento/magento2/pull/5200) -- Adminhtml sales view - Escape remoteIp (by @convenient) + * [#4294](https://github.com/magento/magento2/pull/4294) -- Static deploy flags (by @denisristic) + * [#3137](https://github.com/magento/magento2/pull/3137) -- Update nginx.conf.sample (by @thaiphan) + * [#3746](https://github.com/magento/magento2/pull/3746) -- Add HttpInterface methods and add up-casts for type safety (by @Vinai) + * [#4354](https://github.com/magento/magento2/pull/4354) -- Adds test for getDataUsingMethod using digits (by @fooman) + * [#4396](https://github.com/magento/magento2/pull/4396) -- Don't hardcode the Magento_Backend::admin index (by @annybs) + * [#4491](https://github.com/magento/magento2/pull/4491) -- Correction: variable naming for user roles & role id (by @MagePsycho) + * [#4519](https://github.com/magento/magento2/pull/4519) -- Create auth.json.sample (by @rafaelstz) + * [#3688](https://github.com/magento/magento2/pull/3688) -- added partial fix for issue #2617. (by @whizkid79) + * [#3770](https://github.com/magento/magento2/pull/3770) -- Bugfix: Unable to activate search form on phone (by @vovayatsyuk) + * [#4011](https://github.com/magento/magento2/pull/4011) -- Fixed post var name for update attributes (by @Corefix) + * [#2791](https://github.com/magento/magento2/pull/2791) -- Update Template.php (by @liam-wiltshire) + * [#5496](https://github.com/magento/magento2/pull/5496) -- Fixes #5495 (Mobile navigation submenus need two clicks to open) (by @ajpevers) + * [#5725](https://github.com/magento/magento2/pull/5725) -- Fix/translation in validation files (by @dvynograd) + * [#5915](https://github.com/magento/magento2/pull/5915) -- Added missing translation to range grid filter (by @maqlec) + * [#5884](https://github.com/magento/magento2/pull/5884) -- Use alias already defined in requirejs-config.js (by @kassner) + * [#4388](https://github.com/magento/magento2/pull/4388) -- Fixed column description for "website_id" column (by @ikk0) + * [#1628](https://github.com/magento/magento2/pull/1628) -- Add cache of configuration files list (by @otakarmare) + * [#4791](https://github.com/magento/magento2/pull/4791) -- Replace fabpot/php-cs-fixer with friendsofphp/php-cs-fixer (by @GordonLesti) + * [#5983](https://github.com/magento/magento2/pull/5983) -- Fix 'Track your order' i18n. (by @peec) + * [#1988](https://github.com/magento/magento2/pull/1988) -- Use inline elements for inline links (by @chicgeek) + * [#6283](https://github.com/magento/magento2/pull/6283) -- Update elements.xsd (by @aholovan) + * [#1935](https://github.com/magento/magento2/pull/1935) -- Added 'target' attribute to the allowed attributes array for link block (by @fcapua-summa) + * [#4733](https://github.com/magento/magento2/pull/4733) -- Escape Js Quote for layout updates (by @bchatard) + * [#4565](https://github.com/magento/magento2/pull/4565) -- Update Container.php (by @Maddesto) + * [#4845](https://github.com/magento/magento2/pull/4845) -- Set return code for SetModeCommand (by @mc388) + * [#2037](https://github.com/magento/magento2/pull/2037) -- Correct the error message when creating wrong object for block (by @hiephm) + * [#3779](https://github.com/magento/magento2/pull/3779) -- Add dispatching of view_block_abstract_to_html_after event (by @aleron75) + * [#5741](https://github.com/magento/magento2/pull/5741) -- Update AbstractTemplate.php (by @vivek201) + * [#5145](https://github.com/magento/magento2/pull/5145) -- \Magento\CatalogInventory\Model\Stock\Status->getStockId() to return correct value (by @fe-lix-) + * [#6009](https://github.com/magento/magento2/pull/6009) -- Update README.md (by @fooman) + * [#6280](https://github.com/magento/magento2/pull/6280) -- Fixed layout for customer authentication popup (by @rogyar) + * [#6549](https://github.com/magento/magento2/pull/6549) -- Remove obsolete comment in catalog_category_view.xml (by @pdanzinger) + * [#6804](https://github.com/magento/magento2/pull/6804) -- Fix nav not working in mobile (by @slackerzz) + * [#6801](https://github.com/magento/magento2/pull/6801) -- Fixed unclosed span tag in Review module (by @PingusPepan) + * [#5045](https://github.com/magento/magento2/pull/5045) -- Fix runner for JsTestDriver based tests on OS X (by @Vinai) + * [#4958](https://github.com/magento/magento2/pull/4958) -- block newsletter title (by @slackerzz) + * [#5548](https://github.com/magento/magento2/pull/5548) -- Fix error with the WYSIWYG and Greek characters (by @sakisplus) + * [#7256](https://github.com/magento/magento2/pull/7256) -- Fix incorrect table name during catalog product indexing (by @nagno) + * [#6972](https://github.com/magento/magento2/pull/6972) -- Fix issue #6968 since checkout success title is not displayed (by @AngelVazquezArroyo) + * [#4121](https://github.com/magento/magento2/pull/4121) -- Ensure composer.json exists (by @dank00) + * [#4134](https://github.com/magento/magento2/pull/4134) -- Added call to action to compile command error (by @sammarcus) + * [#6974](https://github.com/magento/magento2/pull/6974) -- Fixed calling non exists order address->getCountry to getCountryId (by @magexo) + * [#4088](https://github.com/magento/magento2/pull/4088) -- Fix newsletter queue subscribers adding performance (by @DariuszMaciejewski) + * [#6952](https://github.com/magento/magento2/pull/6952) -- Update AccountLock.php (by @klict) + * [#5465](https://github.com/magento/magento2/pull/5465) -- Fix Magento\Review\Model\ResourceModel\Rating\Option not instantiable in setup scripts (by @adragus-inviqa) + * [#7799](https://github.com/magento/magento2/pull/7799) -- Remove duplicate code from template file. (by @dverkade) + * [#7919](https://github.com/magento/magento2/pull/7919) -- Using Dynamic Protocol Concatination (by @brobie) + * [#7921](https://github.com/magento/magento2/pull/7921) -- Removed un-used static version rewrite rule in nginx.conf.sample (by @careys7) + * [#8019](https://github.com/magento/magento2/pull/8019) -- added fr_CH to allowed locales list (by @annapivniak) + * [#8082](https://github.com/magento/magento2/pull/8082) -- issue 8080: Cron configuration loading from DB doesn't work (by @ytorbyk) + * [#8062](https://github.com/magento/magento2/pull/8062) -- Fix theme source model used in widget grid (by @Zefiryn) + * [#8232](https://github.com/magento/magento2/pull/8232) -- Fix notice during DI compilation (by @qrz-io) + * [#8306](https://github.com/magento/magento2/pull/8306) -- Remove warning from setup/src/Magento/Setup/Module/I18n/Dictionary/Writer/Csv.php (by @dmanners) + * [#8185](https://github.com/magento/magento2/pull/8185) -- Fix #7461 using the simplest approach for now (by @lazyguru) + * [#8183](https://github.com/magento/magento2/pull/8183) -- [BUGFIX] Fixed the credit memo guest email (by @nickgraz) + * [#8161](https://github.com/magento/magento2/pull/8161) -- Return focus to search input after closing autocomplete dropdown (by @evktalo) + * [#8155](https://github.com/magento/magento2/pull/8155) -- Correct php doc (by @angelomaragna) + * [#8341](https://github.com/magento/magento2/pull/8341) -- Use better function for 'Continue Shopping' url (by @dmatthew) + * [#8336](https://github.com/magento/magento2/pull/8336) -- fixing time format on admin sales order grid (by @magexo) + * [#8327](https://github.com/magento/magento2/pull/8327) -- Change order of parameters passed to LogicException in AbstractTemplate.php (by @bery) + * [#8307](https://github.com/magento/magento2/pull/8307) -- Allow digits in communication class type definition (by @cmuench) + * [#8354](https://github.com/magento/magento2/pull/8354) -- Display correctly "Add" button label for the block class \Magento\Con… (by @diglin) + * [#8246](https://github.com/magento/magento2/pull/8246) -- Fixes #7723 - saving multi select field in UI component form (by @Zefiryn) + * [#8353](https://github.com/magento/magento2/pull/8353) -- Replace into the layout adminhtml_order_shipment_new.xml block alias … (by @diglin) + * [#8395](https://github.com/magento/magento2/pull/8395) -- Added "editPost" action for customer sections.xml (by @rossluk) + * [#8383](https://github.com/magento/magento2/pull/8383) -- Issue/8382 (by @PascalBrouwers) + * [#8151](https://github.com/magento/magento2/pull/8151) -- Remove "<2.7" constraint on symfony/console (by @nicolas-grekas) + * [#8416](https://github.com/magento/magento2/pull/8416) -- Use configured product attributes in wishlist item collection (by @schmengler) + * [#8414](https://github.com/magento/magento2/pull/8414) -- Replace toGMTString with toUTCString (by @schmengler) + * [#8419](https://github.com/magento/magento2/pull/8419) -- Use page result instead of rendering layout directly in controllers (by @schmengler) + * [#8331](https://github.com/magento/magento2/pull/8331) -- Remove Zend1 Json from Magento Captcha module (by @dmanners) + * [#8252](https://github.com/magento/magento2/pull/8252) -- Customer account edit form: additional info block visible (by @Sylvco) + * [#8405](https://github.com/magento/magento2/pull/8405) -- Remove the unused Ajax/Serializer.php class (by @dmanners) + * [#8402](https://github.com/magento/magento2/pull/8402) -- Remove Zend1 db from captcha module (by @dmanners) + * [#8463](https://github.com/magento/magento2/pull/8463) -- Remove outdated comment (by @evktalo) + * [#8456](https://github.com/magento/magento2/pull/8456) -- Specifically ask for the Json Serializer object Mage Braintree (by @dmanners) + * [#8446](https://github.com/magento/magento2/pull/8446) -- Fix "each()" function call on potentially invalid data (by @giacmir) + * [#5928](https://github.com/magento/magento2/pull/5928) -- prevent double shipping method selection (by @danslo) + * [#8217](https://github.com/magento/magento2/pull/8217) -- Update DataProvider.php (by @redelschaap) + * [#8413](https://github.com/magento/magento2/pull/8413) -- Load translations for area, fixes #8412 (by @fooman) + * [#8356](https://github.com/magento/magento2/pull/8356) -- Remove Zend1 captcha from Magento2 Captcha module (by @dmanners) + * [#8474](https://github.com/magento/magento2/pull/8474) -- Fix for https://github.com/magento/magento2/issues/8287 (by @ericrisler) + * [#8487](https://github.com/magento/magento2/pull/8487) -- Mark the STDO test as skipped (by @dmanners) + * [#3155](https://github.com/magento/magento2/pull/3155) -- Remove unused variables in unit test (by @fooman) + * [#6049](https://github.com/magento/magento2/pull/6049) -- Added typo correction for table name comment (by @atishgoswami) + * [#4370](https://github.com/magento/magento2/pull/4370) -- update Catalog Helper (by @barbarich-p) + * [#4106](https://github.com/magento/magento2/pull/4106) -- Adds support for purging varnish cache based on an X-Pool header (by @davidalger) + * [#7982](https://github.com/magento/magento2/pull/7982) -- Add bin/magento commands to list store/website data (by @convenient) + * [#8434](https://github.com/magento/magento2/pull/8434) -- Problem on mobile when catalog gallery allowfullscreen is false #5808 (by @Crossmotion) + * [#8417](https://github.com/magento/magento2/pull/8417) -- add anonymize ip option for google analytics (by @thomas-villagers) + * [#8498](https://github.com/magento/magento2/pull/8498) -- Product->save() shouldn't be called directly (by @stansm) + * [#8581](https://github.com/magento/magento2/pull/8581) -- Fixed overly bold icons in Firefox Mac (by @TandyCorp) + * [#8481](https://github.com/magento/magento2/pull/8481) -- Remove zend json checkout (by @dmanners) + * [#8518](https://github.com/magento/magento2/pull/8518) -- Change link text from "Report Bugs" to "Report a Bug" (by @Zifius) + * [#8514](https://github.com/magento/magento2/pull/8514) -- Add missing name attributes to catalog_product_view layout xml (by @andrewnoble) + * [#8505](https://github.com/magento/magento2/pull/8505) -- Update the Adminhtml image tree JSON (by @dmanners) + * [#8467](https://github.com/magento/magento2/pull/8467) -- Fix for magento/magento2#8392 (by @kirashet666) + * [#8617](https://github.com/magento/magento2/pull/8617) -- Remove Zend_Json from Customer module (by @dmanners) + * [#8609](https://github.com/magento/magento2/pull/8609) -- [PHP 7.1 Compatibility] Void became a reserved word (by @orlangur) + * [#8611](https://github.com/magento/magento2/pull/8611) -- Fix #8308: test setup/src/Magento/Setup/Test/Unit/Model/Cron/JobSetCacheTest.php crashes in debug mode (by @orlangur) + * [#8610](https://github.com/magento/magento2/pull/8610) -- Fix #8315: test setup/src/Magento/Setup/Test/Unit/Module/I18n/Dictionary/Writer/Csv/StdoTest.php crashes in debug mode (by @orlangur) + * [#7339](https://github.com/magento/magento2/pull/7339) -- Fix/xml parser issue (by @dvynograd) + * [#7345](https://github.com/magento/magento2/pull/7345) -- Fix typo (by @mpchadwick) + * [#7406](https://github.com/magento/magento2/pull/7406) -- MAGETWO-60448 (by @vasilii-b) + * [#8000](https://github.com/magento/magento2/pull/8000) -- Set static html fragments as cacheable (by @paveq) + * [#8077](https://github.com/magento/magento2/pull/8077) -- Type cast (by @deriknel) + * [#8278](https://github.com/magento/magento2/pull/8278) -- bug #8277 fixing bug with https downloading. (by @clementbeudot) + * [#8420](https://github.com/magento/magento2/pull/8420) -- Refactor contact module (by @schmengler) + * [#8519](https://github.com/magento/magento2/pull/8519) -- Make "is_required" and "is_visible" properties of telephone, company and fax attributes of addresses configurable (by @avstudnitz) + * [#8537](https://github.com/magento/magento2/pull/8537) -- Fixed missing echo statement in checkout cart coupon template (by @FrankRuis) + * [#8602](https://github.com/magento/magento2/pull/8602) -- Fixed crosssells count always null (by @koenner01) + * [#8593](https://github.com/magento/magento2/pull/8593) -- Fix #7371 (by @rossluk) + * [#8642](https://github.com/magento/magento2/pull/8642) -- Update Save.php (by @josefbehr) + * [#8633](https://github.com/magento/magento2/pull/8633) -- Fix #8632 (by @koenner01) + * [#8513](https://github.com/magento/magento2/pull/8513) -- Replace ext name spaces with dashes (as composer does when storing) (by @AydinHassan) + * [#8668](https://github.com/magento/magento2/pull/8668) -- Add correct return type in order service (by @cmuench) + * [#8677](https://github.com/magento/magento2/pull/8677) -- Fixed Doc Block for the dispatch method of the Rest Controller (by @vrann) + * [#3585](https://github.com/magento/magento2/pull/3585) -- Remove duplicate switchIsFilterable (by @GordonLesti) + * [#8132](https://github.com/magento/magento2/pull/8132) -- Fix incorrect schema definition for price (by @unfunco) + * [#2275](https://github.com/magento/magento2/pull/2275) -- Updated links to point to http://devdocs.magento.com/guides/v2.0 (by @wjarka) + * [#8683](https://github.com/magento/magento2/pull/8683) -- Fixed return type of OrderRepository::getList (by @clementbeudot) + * [#8682](https://github.com/magento/magento2/pull/8682) -- Remove unused argument (by @mfdj) + * [#2185](https://github.com/magento/magento2/pull/2185) -- unused variable (by @barbarich-p) + * [#7894](https://github.com/magento/magento2/pull/7894) -- Fix #7893 (by @andreaspenz) + * [#8623](https://github.com/magento/magento2/pull/8623) -- Fix check for boolean product attributes (by @TKlement) + * [#8678](https://github.com/magento/magento2/pull/8678) -- Fixed Issue #8425 (by @DavidLambauer) + * [#8711](https://github.com/magento/magento2/pull/8711) -- Remove Zend_Json from the persistent module remember me status observer (by @dmanners) + * [#8706](https://github.com/magento/magento2/pull/8706) -- Remove Zend_Json from the unit test in the CustomerImportExport module (by @dmanners) + * [#8723](https://github.com/magento/magento2/pull/8723) -- Add active class to search form wrapper for more theming flexibility (by @andrewkett) + * [#8768](https://github.com/magento/magento2/pull/8768) -- Fix issue #8709 (by @renatocason) + * [#8762](https://github.com/magento/magento2/pull/8762) -- Fix issue #2558 (by @renatocason) + * [#8759](https://github.com/magento/magento2/pull/8759) -- magento/magento2#8618: Apply coupon code button issue on Checkout (by @mcspronko) + * [#8776](https://github.com/magento/magento2/pull/8776) -- Range filter doesn't works with 0 values in admin #7103 (by @giacmir) + * [#7611](https://github.com/magento/magento2/pull/7611) -- Update catalog_rule_form.xml edit labels (by @fernandofauth) + * [#7650](https://github.com/magento/magento2/pull/7650) -- Add host header to varnish cache purge request (by @m0zziter) + * [#7914](https://github.com/magento/magento2/pull/7914) -- FPC JS - Fix for CORS issue (by @OZZlE) + * [#8013](https://github.com/magento/magento2/pull/8013) -- Update resets.html (by @ryantfowler) + * [#8041](https://github.com/magento/magento2/pull/8041) -- Fix USPS Priority Mail to Canada (by @jaywilliams) + * [#8014](https://github.com/magento/magento2/pull/8014) -- Update _resets.less (by @ryantfowler) + * [#8053](https://github.com/magento/magento2/pull/8053) -- Strict checking types during di compilation (by @michalderlatka) + * [#8048](https://github.com/magento/magento2/pull/8048) -- Consistent HTML tags and breaks (by @chickenland) + * [#8056](https://github.com/magento/magento2/pull/8056) -- update allowed container tags (by @steros) + * [#8158](https://github.com/magento/magento2/pull/8158) -- Fix OAuth request helper to support Authorization header value parsing with non-leading OAuth key (by @careys7) + * [#8589](https://github.com/magento/magento2/pull/8589) -- Set correct primary keys for temporary tables in product flat indexer (by @jarnooravainen) + * [#8736](https://github.com/magento/magento2/pull/8736) -- Update Stock.php (by @Corefix) + * [#8119](https://github.com/magento/magento2/pull/8119) -- Throw exception for invalid (missing) template in dev mode (by @convenient) + * [#8690](https://github.com/magento/magento2/pull/8690) -- Fix SALES_ORDER_TAX_ITEM_TAX_ID_ITEM_ID duplicates (by @mimarcel) + * [#8743](https://github.com/magento/magento2/pull/8743) -- Validate PHP classnames in di.xml files via schema (by @ktomk) + * [#8714](https://github.com/magento/magento2/pull/8714) -- Quote values in IN() predicate to avoid cast issues (by @xi-ao) + * [#8835](https://github.com/magento/magento2/pull/8835) -- Replace Zend_Json from the Magento Review module (by @dmanners) + * [#8832](https://github.com/magento/magento2/pull/8832) -- Replace Zend_Json from the Magento Quote module (by @dmanners) + * [#8839](https://github.com/magento/magento2/pull/8839) -- Rename to DataObjectTest (by @mfdj) + * [#2199](https://github.com/magento/magento2/pull/2199) -- Rename admin sidebar Products to Catalog #2060 (by @markoshust) + * [#5620](https://github.com/magento/magento2/pull/5620) -- Remove superfluous method call. (by @maksim-grib) + * [#6767](https://github.com/magento/magento2/pull/6767) -- Removed setting routerlist twice. (by @dverkade) + * [#6257](https://github.com/magento/magento2/pull/6257) -- Fix "none" is not meant to be translated in catalog_product_view layout file (by @azanelli) + * [#7645](https://github.com/magento/magento2/pull/7645) -- Storecode in url changed from default to all (by @bartlubbersen) + * [#7864](https://github.com/magento/magento2/pull/7864) -- Bugfix: Fix for not respected alternative headers in maintenance mode (by @cmuench) + * [#7794](https://github.com/magento/magento2/pull/7794) -- Check return value for getProduct() in getPrice(). (by @pbaylies) + * [#8052](https://github.com/magento/magento2/pull/8052) -- Update PurgeCache.php (by @bery) + * [#8130](https://github.com/magento/magento2/pull/8130) -- Update Options.php (by @redelschaap) + * [#8079](https://github.com/magento/magento2/pull/8079) -- FIX Backend mass delete Sql error (by @asubit) + * [#7234](https://github.com/magento/magento2/pull/7234) -- Lossless images optimalization (by @Igloczek) + * [#8685](https://github.com/magento/magento2/pull/8685) -- [PSR-2 Compliance] Fix #8612: Hundreds of PHPCS-based static tests violations in mainline (by @orlangur) + * [#7568](https://github.com/magento/magento2/pull/7568) -- Minor Update Mysql.php (by @WJdeBaas) + * [#7598](https://github.com/magento/magento2/pull/7598) -- Bugfix for _getProductCollection on a product page (by @evgk) + * [#8886](https://github.com/magento/magento2/pull/8886) -- Fix typo in app/code/Magento/Email/Test/Unit/Model/TemplateTest.php (by @orlangur) + * [#5029](https://github.com/magento/magento2/pull/5029) -- Add default swatch_image placeholder (by @Bartlomiejsz) + * [#6838](https://github.com/magento/magento2/pull/6838) -- Removed preference from di.xml (by @dverkade) + * [#7578](https://github.com/magento/magento2/pull/7578) -- Don't skip attribute options with a value of 0 from the layered navigation (by @dcabrejas) + * [#7615](https://github.com/magento/magento2/pull/7615) -- Fixes duplicate messages shown when adding items to cart (by @comdiler) + * [#7590](https://github.com/magento/magento2/pull/7590) -- Reset skippedRows array in clear method (by @ccasciotti) + * [#7701](https://github.com/magento/magento2/pull/7701) -- Allows you to have 0 as a option (by @Corefix) + * [#7748](https://github.com/magento/magento2/pull/7748) -- Remove _required class from ZIP field (by @dmitryshkolnikov) + * [#7743](https://github.com/magento/magento2/pull/7743) -- MAGETWO-61828: Text swatch "zero" not shown (by @oroskodias) + * [#8883](https://github.com/magento/magento2/pull/8883) -- Fix a typo (by @evgk) + * [#7541](https://github.com/magento/magento2/pull/7541) -- Added "Add / update" comment. Update was missing. (by @gastondisacco) + * [#8808](https://github.com/magento/magento2/pull/8808) -- Change link text from "Report a Bug" to "Report an Issue" (by @Zifius) + * [#8907](https://github.com/magento/magento2/pull/8907) -- Removed unused dependencys (by @ikrs) + * [#8910](https://github.com/magento/magento2/pull/8910) -- Prevent overwriting grunt config (by @Igloczek) + * [#7562](https://github.com/magento/magento2/pull/7562) -- Update AbstractCart.php (by @Corefix) + * [#8766](https://github.com/magento/magento2/pull/8766) -- fix $childrenWrapClass never used (by @slackerzz) + * [#8822](https://github.com/magento/magento2/pull/8822) -- Upgrade PHP CS Fixer to v2 (by @keradus) + * [#8901](https://github.com/magento/magento2/pull/8901) -- Update design_config_form.xml (by @WaPoNe) + * [#8896](https://github.com/magento/magento2/pull/8896) -- Fix all words without " (by @rafaelstz) + * [#8912](https://github.com/magento/magento2/pull/8912) -- magento/magento2#8590: M2.1.4 : ArrayBackend cannot save and Added country regions for Croatia (by @nkajic) + * [#8909](https://github.com/magento/magento2/pull/8909) -- magento/magento2#8863: Malta zipcode validation incomplete (by @mmacinko) + * [#2829](https://github.com/magento/magento2/pull/2829) -- Update final_price.phtml (by @liam-wiltshire) + * [#3869](https://github.com/magento/magento2/pull/3869) -- Optional PHP7.0 Socket Path (by @digimix) + * [#4854](https://github.com/magento/magento2/pull/4854) -- Fix admin menu bug (by @NikolasSumrak) + * [#6400](https://github.com/magento/magento2/pull/6400) -- Fix for Product Attribute's Conditions (by @NikolasSumrak) + * [#6677](https://github.com/magento/magento2/pull/6677) -- Update addCategoriesFilter to return $this (by @maciekpaprocki) + * [#6894](https://github.com/magento/magento2/pull/6894) -- Fixed phpseclib\Net\SFTP constants used in write() method (by @federivo) + * [#7117](https://github.com/magento/magento2/pull/7117) -- Add de_LU and fr_LU languages for Luxembourg (by @ajpevers) + * [#8915](https://github.com/magento/magento2/pull/8915) -- magento/magetno2#8676: I can not translate title attribute in xml fil… (by @DanijelPotocki) + * [#5446](https://github.com/magento/magento2/pull/5446) -- Fix Rest Api - GET /V1/configurable-products/{sku}/children not giving ID in response (by @k-andrew) + * [#6321](https://github.com/magento/magento2/pull/6321) -- Add missing return in resolveShippingRates (by @GordonLesti) + * [#6707](https://github.com/magento/magento2/pull/6707) -- Issue/6706 (by @PascalBrouwers) + * [#6794](https://github.com/magento/magento2/pull/6794) -- Move cache to instance of price box widget (by @JamesonNetworks) + * [#6856](https://github.com/magento/magento2/pull/6856) -- Issue/6855 (by @PascalBrouwers) + * [#6837](https://github.com/magento/magento2/pull/6837) -- Removed argument from di.xml (by @dverkade) + * [#6878](https://github.com/magento/magento2/pull/6878) -- Media attribute folder to be ignored (by @rafaelstz) + * [#6914](https://github.com/magento/magento2/pull/6914) -- Put the contants in the same docblock together (by @dverkade) + * [#6913](https://github.com/magento/magento2/pull/6913) -- Removed commented out line for ignoring coding standards (by @dverkade) + * [#7142](https://github.com/magento/magento2/pull/7142) -- [Framework][Translate] Changed translation order to allow language (by @JSchlarb) + * [#7352](https://github.com/magento/magento2/pull/7352) -- Product export duplicate rows for product with html special chars in data. Bugfix for #7350 (by @comdiler) + * [#8445](https://github.com/magento/magento2/pull/8445) -- Changed name of dispatched object in delete event (by @clementbeudot) + * [#8959](https://github.com/magento/magento2/pull/8959) -- Fixes #2461 (by @ajpevers) + * [#8950](https://github.com/magento/magento2/pull/8950) -- Remove deadcode (by @jipjop) + * [#3469](https://github.com/magento/magento2/pull/3469) -- respect depends declaration in system.xml for form element (by @phoenix-bjoern) + * [#5362](https://github.com/magento/magento2/pull/5362) -- Make CMS directive filtering error be more generic (by @erikhansen) + * [#6354](https://github.com/magento/magento2/pull/6354) -- Newsletter Email fix (by @inettman) + * [#6744](https://github.com/magento/magento2/pull/6744) -- add-product-image-label-bug (by @markpol) + * [#6773](https://github.com/magento/magento2/pull/6773) -- Allow to insert Persian characters for attributes! (by @sIiiS) + * [#6779](https://github.com/magento/magento2/pull/6779) -- Changed documentation of the cache variable (by @dverkade) + * [#6775](https://github.com/magento/magento2/pull/6775) -- Changed constructor to use interface instead of direct classname (by @dverkade) + * [#6971](https://github.com/magento/magento2/pull/6971) -- fix #6961 (by @razbakov) + * [#7097](https://github.com/magento/magento2/pull/7097) -- You -> Your (by @avitex) + * [#7414](https://github.com/magento/magento2/pull/7414) -- Fix typo (by @convenient) + * [#8005](https://github.com/magento/magento2/pull/8005) -- Prevent cross origin iframe content reading (by @Igloczek) + * [#8753](https://github.com/magento/magento2/pull/8753) -- Added Translation for required Data-Attribute (by @DavidLambauer) + * [#8778](https://github.com/magento/magento2/pull/8778) -- magento/magento2: #8765 (by @cavalier79) + * [#8914](https://github.com/magento/magento2/pull/8914) -- magento/magetno2#8529:Typo in error message "Table is not exists" (by @bvrbanec) + * [#2448](https://github.com/magento/magento2/pull/2448) -- Fix Inconsistency (by @srenon) + * [#2093](https://github.com/magento/magento2/pull/2093) -- Remove call to load() in getChildrenCategories method (by @davidalger) + * [#4179](https://github.com/magento/magento2/pull/4179) -- fix typo in Magento_Catalog toolbar less source (by @gil--) + * [#5078](https://github.com/magento/magento2/pull/5078) -- Rename $websiteId to $scopeId (by @flancer64) + * [#5207](https://github.com/magento/magento2/pull/5207) -- Table name fix - rule_customer to salesrule_customer (by @Bartlomiejsz) + * [#5858](https://github.com/magento/magento2/pull/5858) -- Save shipping discount in $total (by @flancer64) + * [#6811](https://github.com/magento/magento2/pull/6811) -- Unable to save subscription checkbox on Admin customer save (by @rich1990) + * [#6839](https://github.com/magento/magento2/pull/6839) -- Changed constructor to use an interface (by @dverkade) + * [#6912](https://github.com/magento/magento2/pull/6912) -- Changed module readme text (by @dverkade) + * [#7262](https://github.com/magento/magento2/pull/7262) -- Replace boolean cast to be able to disable frame, aspect ratio, trans… (by @joost-florijn-kega) + * [#7762](https://github.com/magento/magento2/pull/7762) -- change getId() to getPaymentId() (by @HirokazuNishi) + * [#8769](https://github.com/magento/magento2/pull/8769) -- magento/magento2#7860: Invalid comment for the method __order in Mage… (by @mcspronko) + * [#8917](https://github.com/magento/magento2/pull/8917) -- imagento/magento2#8515: Downloadable product is available for downloa… (by @nazarpadalka) + * [#8908](https://github.com/magento/magento2/pull/8908) -- magento/magento2#8871: Typo in Pull Request Template (by @tomislavsantek) + * [#8989](https://github.com/magento/magento2/pull/8989) -- Remove redundant check in if-condition (by @FabianLauer) + * [#8953](https://github.com/magento/magento2/pull/8953) -- Log level for caught exception (by @flancer64) + * [#8994](https://github.com/magento/magento2/pull/8994) -- Syntax fix (by @rafaelstz) + * [#1895](https://github.com/magento/magento2/pull/1895) -- Fix relative template references in individual Magento modules (by @davidalger) + * [#4224](https://github.com/magento/magento2/pull/4224) -- Update get.php (by @thaiphan) + * [#6567](https://github.com/magento/magento2/pull/6567) -- Always skip hidden files (by @quickshiftin) + * [#6989](https://github.com/magento/magento2/pull/6989) -- Allow extending config variables (by @adragus-inviqa) + * [#7218](https://github.com/magento/magento2/pull/7218) -- Set store id on block only when empty (by @dank00) + * [#7161](https://github.com/magento/magento2/pull/7161) -- Fix a bug resulting in incorrect offsets with dynamic row drag-n-drop functionality (by @navarr) + * [#7664](https://github.com/magento/magento2/pull/7664) -- Fix for "Stock Status" field not disappearing in admin when "Manage Stock" = No (by @comdiler) + * [#8812](https://github.com/magento/magento2/pull/8812) -- Fix count SQL on products sold collection (by @jameshalsall) + * [#8991](https://github.com/magento/magento2/pull/8991) -- Update AbstractModel.php (by @redelschaap) + * [#9001](https://github.com/magento/magento2/pull/9001) -- Fix #3791 (by @quienti) + * [#8998](https://github.com/magento/magento2/pull/8998) -- Fixed empty submenu group in backend menu (by @vovayatsyuk) + * [#8928](https://github.com/magento/magento2/pull/8928) -- Stop $this->validColumnNames array from growing and growing (by @jalogut) + * [#4149](https://github.com/magento/magento2/pull/4149) -- Fix wording of downloadable Products (by @bh-ref) + * [#4674](https://github.com/magento/magento2/pull/4674) -- Refactor repeating logic (by @nevvermind) + * [#4501](https://github.com/magento/magento2/pull/4501) -- Add Crowdin badge - official translations (by @piotrekkaminski) + * [#6243](https://github.com/magento/magento2/pull/6243) -- Set getConnection() as public method (by @flancer64) + * [#6250](https://github.com/magento/magento2/pull/6250) -- Return the same data on exit (by @flancer64) + * [#4844](https://github.com/magento/magento2/pull/4844) -- Fix typo (by @orlangur) + * [#4874](https://github.com/magento/magento2/pull/4874) -- Make NL zipcode pattern less strict (by @tdgroot) + * [#5400](https://github.com/magento/magento2/pull/5400) -- Privacy Policy translation (fixes Issue #2951) (by @MindConflicts) + * [#5671](https://github.com/magento/magento2/pull/5671) -- Fix small typo (by @adragus-inviqa) + * [#6132](https://github.com/magento/magento2/pull/6132) -- Remove an extra space while clearing indexed stock items. (by @nntoan) + * [#6840](https://github.com/magento/magento2/pull/6840) -- Removed unused variable $routerId (by @dverkade) + * [#6834](https://github.com/magento/magento2/pull/6834) -- Removed default values for title, meta description, better labels. (by @paales) + * [#7124](https://github.com/magento/magento2/pull/7124) -- Code documentation corrections (by @evktalo) + * [#7294](https://github.com/magento/magento2/pull/7294) -- Let less continue compilation if file is empty (by @timo-schmid) + * [#8648](https://github.com/magento/magento2/pull/8648) -- Remove the copyright year from file headers (by @jameshalsall) + * [#4897](https://github.com/magento/magento2/pull/4897) -- Schedule generation was broken (by @ajpevers) + * [#5503](https://github.com/magento/magento2/pull/5503) -- ACL titles are swapped (by @yireo) + * [#7344](https://github.com/magento/magento2/pull/7344) -- Fix checking active carrier against store (by @torreytsui) + * [#7221](https://github.com/magento/magento2/pull/7221) -- Typo fix (by @gastondisacco) + * [#8982](https://github.com/magento/magento2/pull/8982) -- Move blank theme dependencies out of Magento_Theme requirejs-config (by @mikeoloughlin) + * [#8930](https://github.com/magento/magento2/pull/8930) -- Improved check when CategoryProcessor attempts to create a new category (by @ccasciotti) + * [#8923](https://github.com/magento/magento2/pull/8923) -- Check of null result value in swatch-renderer.js (by @aholovan) + * [#9013](https://github.com/magento/magento2/pull/9013) -- Added JS Jasmine tests to Travis CI (by @Igloczek) + * [#9039](https://github.com/magento/magento2/pull/9039) -- Translation in adminhtml Import form (by @Nolwennig) + * [#9034](https://github.com/magento/magento2/pull/9034) -- Invalidate and refresh customer data sections on HTTP DELETE requests (by @Vinai) + * [#6322](https://github.com/magento/magento2/pull/6322) -- Admin product edit block getHeader is not used (by @kassner) + * [#7045](https://github.com/magento/magento2/pull/7045) -- In case something wrong with underlying products (by @Will-I4M) + * [#8568](https://github.com/magento/magento2/pull/8568) -- Fix quote's outdated shipping address overwriting PayPal Express shipping address (by @torreytsui) + * [#9057](https://github.com/magento/magento2/pull/9057) -- remove duplicate method call (#9017) (by @will-b) + * [#9080](https://github.com/magento/magento2/pull/9080) -- Fix grammar mistakes with subscriptions - fixes #7498 (by @sambolek) + * [#9076](https://github.com/magento/magento2/pull/9076) -- Fix typo (by @PieterCappelle) + * [#9061](https://github.com/magento/magento2/pull/9061) -- Empty resolvedScopeCodes when config cache is cleaned (by @andreas-wickberg-vaimo) + * [#9044](https://github.com/magento/magento2/pull/9044) -- Remove superfluous character in class (by @tkn98) + * [#9095](https://github.com/magento/magento2/pull/9095) -- Added translation to label argument xml. (by @mrkhoa99) + * [#9108](https://github.com/magento/magento2/pull/9108) -- Redundant expression */1 in crontab.xml (by @giacmir) + * [#9170](https://github.com/magento/magento2/pull/9170) -- Corrected class name in documentation. (by @dfelton) + * [#6778](https://github.com/magento/magento2/pull/6778) -- Changed locator class name for ObjectManager (by @dverkade) + * [#7556](https://github.com/magento/magento2/pull/7556) -- Fix merging nested in view.xml (by @torreytsui) + * [#8903](https://github.com/magento/magento2/pull/8903) -- Include Reply-To name in contact form email header (by @josephmcdermott) + * [#9140](https://github.com/magento/magento2/pull/9140) -- remove duplicate calls to initObjectManager in bootstrap class (by @sivajik34) + * [#9133](https://github.com/magento/magento2/pull/9133) -- Favicon folder added on gitignore (by @rafaelstz) + * [#9204](https://github.com/magento/magento2/pull/9204) -- FPT label not translatable in the totals on the cart page. (by @okorshenko) + * [#5043](https://github.com/magento/magento2/pull/5043) -- Change 'select' to 'query' in props (by @flancer64) + * [#5367](https://github.com/magento/magento2/pull/5367) -- Change pub/.htaccess MAGE_MODE comment (by @erikhansen) + * [#5742](https://github.com/magento/magento2/pull/5742) -- Collection walk method bug fix when specific callback function (by @jalogut) + * [#6385](https://github.com/magento/magento2/pull/6385) -- Replace EE License Placeholder text with filename (by @navarr) + * [#6443](https://github.com/magento/magento2/pull/6443) -- Refactor Option ResourceModel to allow price supporting types to be intercepted (by @navarr) + * [#6772](https://github.com/magento/magento2/pull/6772) -- Changed constructor to use an interface (by @dverkade) + * [#6910](https://github.com/magento/magento2/pull/6910) -- Good practice, license in readme file (by @rafaelstz) + * [#7506](https://github.com/magento/magento2/pull/7506) -- Add configurations for change email templates (by @kassner) + * [#7464](https://github.com/magento/magento2/pull/7464) -- Is Allowed Guest Checkout (by @hungvt) + * [#7900](https://github.com/magento/magento2/pull/7900) -- Setting proper resource name (by @ddattee) + * [#8462](https://github.com/magento/magento2/pull/8462) -- Fix product option files not copying to order dir. (by @evktalo) + * [#8824](https://github.com/magento/magento2/pull/8824) -- Popup-Modal not closing on Safari/Windows (by @Hansschouten) + * [#9062](https://github.com/magento/magento2/pull/9062) -- Upgrade JS dependencies (by @Igloczek) + * [#9084](https://github.com/magento/magento2/pull/9084) -- Fix Google Analytics typo in printing Account Number, fixes #7549 (by @sambolek) + * [#9112](https://github.com/magento/magento2/pull/9112) -- Fix attribute label on product page at different store views (by @tufahu) + * [#9103](https://github.com/magento/magento2/pull/9103) -- Cli info di (by @springerin) + * [#9165](https://github.com/magento/magento2/pull/9165) -- Improved text of exception message in case of error in module's composer.json (by @vovayatsyuk) + * [#9131](https://github.com/magento/magento2/pull/9131) -- Fix to allow Zend_Db_Expr as column default (by @scottsb) + * [#9221](https://github.com/magento/magento2/pull/9221) -- Avoid: Undefined index: value in app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php on line 157 in Ajax return (by @mhauri) + * [#9215](https://github.com/magento/magento2/pull/9215) -- Remove unused and invalid method (by @adragus-inviqa) + * [#9210](https://github.com/magento/magento2/pull/9210) -- Do not di:compile tests/ folder (by @kassner) + * [#5325](https://github.com/magento/magento2/pull/5325) -- update url guide to v2.0 (by @sIiiS) + * [#6452](https://github.com/magento/magento2/pull/6452) -- Fix Login Popup broken on iPad portrait (by @ihor-sviziev) + * [#9234](https://github.com/magento/magento2/pull/9234) -- Fix indentation in pub/.htaccess file (by @erikhansen) + * [#6266](https://github.com/magento/magento2/pull/6266) -- Change commit to rollBack on fail (by @flancer64) + * [#6792](https://github.com/magento/magento2/pull/6792) -- Make a hardcoded value in Customizable Options interceptable (by @navarr) + * [#9094](https://github.com/magento/magento2/pull/9094) -- Issue #2802, #1146: Fixing sitemap generation folder (by @JosephMaxwell) + * [#9124](https://github.com/magento/magento2/pull/9124) -- Solve issues with API (by @paales) + * [#9247](https://github.com/magento/magento2/pull/9247) -- Fixed layout handle for cms page (by @simpleadm) + * [#9257](https://github.com/magento/magento2/pull/9257) -- Fixed coding standard violations in the Framework\Message namespace (by @dverkade) + * [#9254](https://github.com/magento/magento2/pull/9254) -- Fixed coding standard violations in the Framework\Encryption namespace (by @dverkade) + * [#9253](https://github.com/magento/magento2/pull/9253) -- Fixed coding standard violations in the Framework\Event namespace (by @dverkade) + * [#9250](https://github.com/magento/magento2/pull/9250) -- Fixed coding standard violations in the Framework\Archive namespace (by @dverkade) + * [#9264](https://github.com/magento/magento2/pull/9264) -- Enable/Disable DB query logging commands (by @federivo) + * [#9260](https://github.com/magento/magento2/pull/9260) -- Fixed coding standard violations in Framework\HTTP namespace: (by @dverkade) + * [#9258](https://github.com/magento/magento2/pull/9258) -- Fixed coding standard violations in the Framework\Filesystem namespace (by @dverkade) + * [#9286](https://github.com/magento/magento2/pull/9286) -- [WIP] Varnish Vcl generator command (by @piotrkwiecinski) + * [#9282](https://github.com/magento/magento2/pull/9282) -- Fixed coding standard violations in the Framework\Filter namespace (by @dverkade) + * [#9281](https://github.com/magento/magento2/pull/9281) -- Fixed coding standard violations in Framework\Image namespace (by @dverkade) + * [#9298](https://github.com/magento/magento2/pull/9298) -- Remove php-5.6 environment from travis.yml (by @4quaternion) + * [#4816](https://github.com/magento/magento2/pull/4816) -- Fixed issue with grouped product name column renderer. It (by @wbyrnetx) + * [#5147](https://github.com/magento/magento2/pull/5147) -- Change value-assignment to a setValue call (by @wexo-team) + * [#5411](https://github.com/magento/magento2/pull/5411) -- #5236 fixes "The configuration parameter "componentType" is a required for "advanced_pricing_button" component" (by @Protazy21) + * [#7148](https://github.com/magento/magento2/pull/7148) -- Fix issue 7075 (by @rmsundar1) + * [#9027](https://github.com/magento/magento2/pull/9027) -- Make CSS minifying compatible with calc() CSS function (by @sambolek) + * [#9262](https://github.com/magento/magento2/pull/9262) -- Remove zend json from theme (by @dmanners) + * [#9261](https://github.com/magento/magento2/pull/9261) -- Remove zend json from weee (by @dmanners) + * [#9302](https://github.com/magento/magento2/pull/9302) -- Fixed coding standard violations in the Framework\Module namespace (by @dverkade) + * [#9299](https://github.com/magento/magento2/pull/9299) -- Improved check for attribute codes in "additional_attributes" field (by @ccasciotti) + * [#9293](https://github.com/magento/magento2/pull/9293) -- Deprecate unused navigation-menu.js file in blank theme (by @mikeoloughlin) + * [#9308](https://github.com/magento/magento2/pull/9308) -- Remove unnecessary FQCN in ObserverInterface (by @jameshalsall) + * [#9304](https://github.com/magento/magento2/pull/9304) -- Fixed coding standard violations in all Factory classes located in app/code (by @dverkade) + * [#9303](https://github.com/magento/magento2/pull/9303) -- Fixed coding standard violations in the Framework namespace (by @dverkade) + * [#9318](https://github.com/magento/magento2/pull/9318) -- Fixed coding standard violations in the Framework\Backup namespace (by @dverkade) + * [#9321](https://github.com/magento/magento2/pull/9321) -- Fixed coding standard violations in the Framework\Autoload, Framework\Session & Framework\Webapi namespaces (by @dverkade) + * [#9320](https://github.com/magento/magento2/pull/9320) -- Fixed coding standard violations in the Framework\Cache namespace (by @dverkade) + * [#9319](https://github.com/magento/magento2/pull/9319) -- Fixed coding standard violations in the Framework\Api namespace (by @dverkade) + * [#9334](https://github.com/magento/magento2/pull/9334) -- Fixed coding standard violations in the Framework\Controller, Framework\CSS, Framework\Phrase and Framework\Pricing namespace (by @dverkade) + * [#9330](https://github.com/magento/magento2/pull/9330) -- Fixed coding standard violations in the Framework\Data namespace (by @dverkade) + * [#9329](https://github.com/magento/magento2/pull/9329) -- Fixed coding standard violations in the Framework\Stdlib namespace (by @dverkade) + * [#9328](https://github.com/magento/magento2/pull/9328) -- Fixed coding standard violations in the Framework\Config namespace (by @dverkade) + * [#5372](https://github.com/magento/magento2/pull/5372) -- Fix filesystem permission issues (by @BlackIkeEagle) + * [#9129](https://github.com/magento/magento2/pull/9129) -- Product Wizard: Use result type Layout instead of page layout (by @klein0r) + * [#9352](https://github.com/magento/magento2/pull/9352) -- Fixed coding standard violations in the Framework\File namespace (by @dverkade) + * [#9351](https://github.com/magento/magento2/pull/9351) -- Fixed coding standard violations in the Framework\Locale namespace (by @dverkade) + * [#9350](https://github.com/magento/magento2/pull/9350) -- Fixed coding standard violations in the Framework\App namespace (by @dverkade) + * [#9355](https://github.com/magento/magento2/pull/9355) -- Fixed coding standard violations in the Framework\Test namespace (by @dverkade) + * [#9354](https://github.com/magento/magento2/pull/9354) -- Fixed coding standard violations in the Framework\Translate namespace (by @dverkade) + * [#9353](https://github.com/magento/magento2/pull/9353) -- Fixed coding standard violations in the Framework\DB namespace (by @dverkade) + * [#8955](https://github.com/magento/magento2/pull/8955) -- Remove context aggregation validation (see Issue #6114) (by @Vinai) + * [#9343](https://github.com/magento/magento2/pull/9343) -- Add logging to contact us form (by @JamesonNetworks) + * [#9414](https://github.com/magento/magento2/pull/9414) -- Use loadPlayer requirejs mapping (by @ntoombs19) + * [#9400](https://github.com/magento/magento2/pull/9400) -- Fix addIdFilter method (by @adrian-martinez-interactiv4) + * [#9363](https://github.com/magento/magento2/pull/9363) -- Add ability to inject exception code in LocalizedException (by @adragus-inviqa) + * [#9446](https://github.com/magento/magento2/pull/9446) -- Fix data deletion using the multiple delete command (by @Kenboy) + * [#9539](https://github.com/magento/magento2/pull/9539) -- fix for "Class Magento\Framework\Console\CLI not found" in case sensitive scenarios (by @EObukhovsky) + * [#9514](https://github.com/magento/magento2/pull/9514) -- Fix breadcrumbs extra space (by @VincentMarmiesse) + * [#8409](https://github.com/magento/magento2/pull/8409) -- Allow X-Forwarded-For to have multiple values (by @kassner) + * [#9093](https://github.com/magento/magento2/pull/9093) -- JS Static tests added to CI (ESLint + JSCS) (by @Igloczek) + * [#9091](https://github.com/magento/magento2/pull/9091) -- ESLint errors fix (by @Igloczek) + * [#9285](https://github.com/magento/magento2/pull/9285) -- Replace Zend_Log with Psr\Log\LoggerInterface (by @tdgroot) + * [#9380](https://github.com/magento/magento2/pull/9380) -- Removed unnecessary code and namespaces from import validators (by @ccasciotti) + * [#9540](https://github.com/magento/magento2/pull/9540) -- Removed workaround for old Webkit bug in the TinyMCE editor for selec… (by @hostep) + * [#9549](https://github.com/magento/magento2/pull/9549) -- Selects correct stores value option (by @Corefix) + * [#9574](https://github.com/magento/magento2/pull/9574) -- no need to create customer once u got the quote object (by @sivajik34) + * [#9618](https://github.com/magento/magento2/pull/9618) -- Flip the property assignments for _logger and _fetchStrategy in __wakeup (by @cykirsch) + * [#9617](https://github.com/magento/magento2/pull/9617) -- Exclude unnecessarily duplicated Travis CI build jobs (by @Igloczek) + * [#9622](https://github.com/magento/magento2/pull/9622) -- Zend_Wildfire deprecated, Firephp outdated, magento/magento2#9239 and magento/magento2#9241 (by @SolsWebdesign) + * [#9625](https://github.com/magento/magento2/pull/9625) -- Remove unused plugin (by @elzekool) + * [#9637](https://github.com/magento/magento2/pull/9637) -- Change "wan't" to "want" (by @Leland) + * [#7020](https://github.com/magento/magento2/pull/7020) -- Fixes #7006, sales_order_status_label does not support version control (by @ajpevers) + * [#7456](https://github.com/magento/magento2/pull/7456) -- Remove unused entity_id foreign key (by @mattjbarlow) + * [#7755](https://github.com/magento/magento2/pull/7755) -- Remove redundant check and return early (by @AydinHassan) + * [#9657](https://github.com/magento/magento2/pull/9657) -- Fixes Typo (by @riconeitzel) + * [#4903](https://github.com/magento/magento2/pull/4903) -- Fix undefined offset notice when no order states are set (by @adragus-inviqa) + * [#6344](https://github.com/magento/magento2/pull/6344) -- Zend instead of regex in getGetterReturnType (by @flancer64) + * [#9686](https://github.com/magento/magento2/pull/9686) -- Update scripts.js (by @redelschaap) + * [#9701](https://github.com/magento/magento2/pull/9701) -- Add missing payment info template for PDF generation (by @cmuench) + * [#9697](https://github.com/magento/magento2/pull/9697) -- Configure Travis CI to run functional tests (by @okolesnyk) + * [#9711](https://github.com/magento/magento2/pull/9711) -- Cookie Restriction Mode Overlay should not be cached by Varnish #6455 (by @bka) + * [#9713](https://github.com/magento/magento2/pull/9713) -- stringify cookie value to fix Google Analyitcs Tracking and Cookie Overlay #5596 (by @bka) + * [#6503](https://github.com/magento/magento2/pull/6503) -- Remove breadcrumbs for multistore homepage (by @PingusPepan) + * [#7330](https://github.com/magento/magento2/pull/7330) -- Fix Framework\Data\Collection::each() method (by @Vinai) + * [#8484](https://github.com/magento/magento2/pull/8484) -- Fix swatch-renderer.js product id and isProductViewExist (by @mimarcel) + * [#9348](https://github.com/magento/magento2/pull/9348) -- Replace framework's Zend_Session interface usage with SessionHandlerInterface (by @tdgroot) + * [#9654](https://github.com/magento/magento2/pull/9654) -- magento/magento2#7279 bill-to name and ship-to name truncated to 20 chars (by @SolsWebdesign) + * [#9627](https://github.com/magento/magento2/pull/9627) -- Fix coding standard in Magento AdminNotification module (by @dverkade) + * [#9714](https://github.com/magento/magento2/pull/9714) -- Can't delete last item in cart if Minimum Order is Enable #6151 (by @storbahn) + * [#9717](https://github.com/magento/magento2/pull/9717) -- use payment method name to make checkbox of agreements more unique #6207 (by @bka) + * [#9715](https://github.com/magento/magento2/pull/9715) -- #4272: v2.0.4 Credit memos with adjustment fees cannot be fully refunded with a second credit memo (by @mcspronko) + * [#9344](https://github.com/magento/magento2/pull/9344) -- Explace the direct usage of Zend_Json with a call to the Json Help class (by @dmanners) + * [#9475](https://github.com/magento/magento2/pull/9475) -- Update select.js (by @redelschaap) + * [#9600](https://github.com/magento/magento2/pull/9600) -- Do not hardcode product link types (by @kassner) + * [#9712](https://github.com/magento/magento2/pull/9712) -- Customer with unique attribute can't be saved #7844 (by @storbahn) + * [#9723](https://github.com/magento/magento2/pull/9723) -- Patch to allow multiple filter_url_params to function (by @southerncomputer) + * [#9721](https://github.com/magento/magento2/pull/9721) -- [BUGFIX][6244] Fix Issue with code label display in cart checkout. (by @diglin) + * [#9753](https://github.com/magento/magento2/pull/9753) -- Replace Zend_Json in the configurable product block test (by @dmanners) + * [#9777](https://github.com/magento/magento2/pull/9777) -- Fix for #5897: getIdentities relies on uninitialized collection (by @kassner) + * [#9772](https://github.com/magento/magento2/pull/9772) -- Allow for referenceBlock to include template argument (by @jissereitsma) + * [#9797](https://github.com/magento/magento2/pull/9797) -- Adding logo in media folder (by @rafaelstz) + * [#9409](https://github.com/magento/magento2/pull/9409) -- Add a name to the Composite\Fieldset\Options block directive (by @navarr) + * [#9665](https://github.com/magento/magento2/pull/9665) -- Fix for javascript "mixins" when 'urlArgs' is set in requirejs - issue 8221 (by @thelettuce) + * [#9835](https://github.com/magento/magento2/pull/9835) -- Fixes Mage.Cookies poor performance (by @wujashek) + * [#9430](https://github.com/magento/magento2/pull/9430) -- Fix wrong store id filter (by @mimarcel) + * [#9670](https://github.com/magento/magento2/pull/9670) -- Allow injection of Magento\Catalog\Model\View\Asset\ImageFactory (by @rolftimmermans) + * [#9778](https://github.com/magento/magento2/pull/9778) -- new CLI command: Enable Template Hints (by @miguelbalparda) + * [#9820](https://github.com/magento/magento2/pull/9820) -- [oauth] Fixes #9819 (by @EliasKotlyar) + * [#9859](https://github.com/magento/magento2/pull/9859) -- Removed unused $_customerSession property (by @edenreich) + * [#4450](https://github.com/magento/magento2/pull/4450) -- Add ability to use tree-massactions ("sub-menus") on Sales > Orders grid (by @ikk0) + * [#9368](https://github.com/magento/magento2/pull/9368) -- Redis sess: fix path for persistent_identifier & compression_threshold (by @LukeHandle) + * [#9690](https://github.com/magento/magento2/pull/9690) -- Add froogaloop library as a dependency to load-player module (by @ntoombs19) + * [#9813](https://github.com/magento/magento2/pull/9813) -- Use static:: to support late static bindings in Invoice and Creditmemo (by @jokeputs) + * [#9780](https://github.com/magento/magento2/pull/9780) -- Coupon codes not showing in invoice print out #9216 (by @naouibelgacem) + * [#9872](https://github.com/magento/magento2/pull/9872) -- Fixed issue causing static test failure to report success on Travis (by @davidalger) + * [#9890](https://github.com/magento/magento2/pull/9890) -- Improved error logging when trying to save a product (by @woutersamaey) + * [#9892](https://github.com/magento/magento2/pull/9892) -- Added .DS_Store to .gitignore for Mac users (by @woutersamaey) + * [#9873](https://github.com/magento/magento2/pull/9873) -- Fixes layered navigation options being cached using the wrong store id. (by @hostep) + * [#7405](https://github.com/magento/magento2/pull/7405) -- Update Curl.php (by @redelschaap) + * [#7780](https://github.com/magento/magento2/pull/7780) -- setup:di:compile returns exit code 0 if errors are found (by @pivulic) + * [#9157](https://github.com/magento/magento2/pull/9157) -- Return array of blocks as items instead of array of arrays (by @tkotosz) + * [#9810](https://github.com/magento/magento2/pull/9810) -- Fix bug linked product position not updated if product link already exists (by @jalogut) + * [#9824](https://github.com/magento/magento2/pull/9824) -- Email to a Friend feature (by @WaPoNe) + * [#9823](https://github.com/magento/magento2/pull/9823) -- Return array of pages as items instead of array of arrays (by @tkotosz) + * [#9922](https://github.com/magento/magento2/pull/9922) -- Fixes small backwards incompatibility issue created in MAGETWO-69728 (by @hostep) + * [#4891](https://github.com/magento/magento2/pull/4891) -- Remove faulty index subscription (by @ajpevers) + * [#7758](https://github.com/magento/magento2/pull/7758) -- Throw exception when attribute doesn't exitst (by @AydinHassan) + * [#8879](https://github.com/magento/magento2/pull/8879) -- add middle name to checkout address html templates #8878 (by @ajpevers) + * [#9251](https://github.com/magento/magento2/pull/9251) -- Fixed coding standard violations in the Framework\Validator namespace (by @dverkade) + * [#9525](https://github.com/magento/magento2/pull/9525) -- Fixed the Inconsistent Gift Options checkbox labels #9421 (by @vpiyappan) + * [#9905](https://github.com/magento/magento2/pull/9905) -- Fix composer validation (by @barbazul) + * [#9932](https://github.com/magento/magento2/pull/9932) -- Fix typo in comment (by @avoelkl) + * [#9306](https://github.com/magento/magento2/pull/9306) -- Fix PaymentTokenFactory interface to have the "Interface" at the end of the name. (by @dverkade) + * [#9391](https://github.com/magento/magento2/pull/9391) -- Fix depends per group in system.xml (by @osrecio) + * [#9902](https://github.com/magento/magento2/pull/9902) -- Fix static integrity classes tests in Windows (by @barbazul) + * [#9915](https://github.com/magento/magento2/pull/9915) -- suggestion from #9338: add some command/option to the deploy command to refresh the version (by @ajpevers) + * [#9925](https://github.com/magento/magento2/pull/9925) -- Fix #9924, prefill prefix and suffix in checkout shipping address (by @ajpevers) + * [#9941](https://github.com/magento/magento2/pull/9941) -- By default, show times in admin grids in the store timezone. (by @ajpevers) + * [#9943](https://github.com/magento/magento2/pull/9943) -- Cron uses the wrong timestamp method (by @ajpevers) + * [#9964](https://github.com/magento/magento2/pull/9964) -- Add target attribute to Magento_Ui grid (by @thelettuce) + * [#9973](https://github.com/magento/magento2/pull/9973) -- Fixed coding standard violations in the Magento\Wishlist namespace (by @dverkade) + * [#9974](https://github.com/magento/magento2/pull/9974) -- Fixed coding standard violations in the Magento\Backend namespace (by @dverkade) + * [#9975](https://github.com/magento/magento2/pull/9975) -- Fixed coding standard violations in the Magento\Cms namespace (by @dverkade) + * [#9978](https://github.com/magento/magento2/pull/9978) -- Fixed coding standard violations in the Magento\Authorization Magento\Backup Magento\Captcha Magento\CurrencySymbol and Magento\Dhl namespace (by @dverkade) + * [#8965](https://github.com/magento/magento2/pull/8965) -- Reduce calls to SplFileInfo::realpath() in the Magento\Setup\Module\Di\Code\Reader\ClassesScanner class (by @kschroeder) + * [#9996](https://github.com/magento/magento2/pull/9996) -- Ubuntu Trusty 14.04 images update (by @miguelbalparda) + * [#8784](https://github.com/magento/magento2/pull/8784) -- magento/magento2: #8616 (by @cavalier79) + * [#9939](https://github.com/magento/magento2/pull/9939) -- Retrieve taxes from the correct object (by @fooman) + * [#9957](https://github.com/magento/magento2/pull/9957) -- Instantly apply configuration changes in the cron schedule (by @ajpevers) + * [#9994](https://github.com/magento/magento2/pull/9994) -- Fix mini-cart not emptied for logged out users checking out with PayPal Express (by @driskell) + * [#9082](https://github.com/magento/magento2/pull/9082) -- Get sitemap product images from image cache, if available (by @sambolek) + * [#9786](https://github.com/magento/magento2/pull/9786) -- [#7291] Change the default contact form email template to HTML (by @VincentMarmiesse) + * [#9361](https://github.com/magento/magento2/pull/9361) -- Fixed coding standard violations in the Framework\Model namespace (by @dverkade) + * [#9359](https://github.com/magento/magento2/pull/9359) -- Fixed coding standard violations in the Framework\Interception namespace (by @dverkade) + * [#9358](https://github.com/magento/magento2/pull/9358) -- Fixed coding standard violations in the Framework\Code namespace (by @dverkade) + * [#9429](https://github.com/magento/magento2/pull/9429) -- Fix not detecting current store using store code in url using $storeResolver->getCurrentStoreId() (by @mimarcel) + * [#9362](https://github.com/magento/magento2/pull/9362) -- Fixed coding standard violations in the Framework\ObjectManager namespace (by @dverkade) + * [#9970](https://github.com/magento/magento2/pull/9970) -- Added public methods to make Sitemap model plugin friendly (by @7ochem) + * [#7729](https://github.com/magento/magento2/pull/7729) -- Allow USPS Shipping Methods Without ExtraServices (by @jaywilliams) + * [#9314](https://github.com/magento/magento2/pull/9314) -- Support null value for custom attributes. (by @meng-tian) + * [#10033](https://github.com/magento/magento2/pull/10033) -- Fix for file category image uploader (by @Bartlomiejsz) + * [#10047](https://github.com/magento/magento2/pull/10047) -- Include attribute code in error message (by @lazyguru) + * [#10056](https://github.com/magento/magento2/pull/10056) -- Translate password field placeholder in Checkout (by @mimarcel) + * [#7139](https://github.com/magento/magento2/pull/7139) -- Stickyjs improvements (by @vovayatsyuk) + * [#9681](https://github.com/magento/magento2/pull/9681) -- Issue 9680: Use parent name for variations (by @PascalBrouwers) + * [#10031](https://github.com/magento/magento2/pull/10031) -- Allow option disabling for optgroup binding (by @Bart-art) + * [#10060](https://github.com/magento/magento2/pull/10060) -- Adding escapeHtml to Newsletter phtml (by @rafaelstz) + * [#10062](https://github.com/magento/magento2/pull/10062) -- Fix formatting for USPS Carrier (by @ihor-sviziev) + * [#9672](https://github.com/magento/magento2/pull/9672) -- Revert minimum stability to stable, tasks #4359 (by @ktomk) + * [#9986](https://github.com/magento/magento2/pull/9986) -- Improved type hints and declarations for \Magento\Quote\Model\Quote\Address\Total (by @schmengler) + * [#10082](https://github.com/magento/magento2/pull/10082) -- M2 2266 (by @tzyganu) + * [#10086](https://github.com/magento/magento2/pull/10086) -- Fix condition for autoloader function definitions (by @miromichalicka) + * [#9611](https://github.com/magento/magento2/pull/9611) -- Admin Grid Mass action Select / Unselect All issue #9610 (by @minesh0111) + * [#9726](https://github.com/magento/magento2/pull/9726) -- Remove wrong '_setup' replace when getting DB connection (2) (by @jalogut) + * [#9754](https://github.com/magento/magento2/pull/9754) -- Remove zend json from form elements (by @dmanners) + * [#9992](https://github.com/magento/magento2/pull/9992) -- Make page title in layout files translatable (by @ajpevers) + * [#10052](https://github.com/magento/magento2/pull/10052) -- M2 5381 (by @tzyganu) + * [#1563](https://github.com/magento/magento2/pull/1563) -- Convert long form tags with echo to use short-echo tags (by @davidalger) + * [#4147](https://github.com/magento/magento2/pull/4147) -- Add filename paramenter log (by @fcapua-summa) + * [#10043](https://github.com/magento/magento2/pull/10043) -- Fix trailing slash used in url rewrites (by @ihor-sviziev) + * [#10106](https://github.com/magento/magento2/pull/10106) -- Return URL in getThumbnailUrl instead of nothing (by @samgranger) + * [#3889](https://github.com/magento/magento2/pull/3889) -- Add missing dependencies of magento/framework (by @GordonLesti) + * [#10114](https://github.com/magento/magento2/pull/10114) -- Add missing dependencies of magento/framework (by @okorshenko) + * [#7174](https://github.com/magento/magento2/pull/7174) -- Type hint for \DateTimeInterface instead of \DateTime (by @jameshalsall) + * [#9189](https://github.com/magento/magento2/pull/9189) -- Avoid duplicate ltrim function on not complied mode. (by @sivajik34) + * [#10094](https://github.com/magento/magento2/pull/10094) -- Fix for isoneof condition in catalogrule (by @duckchip) + * [#10105](https://github.com/magento/magento2/pull/10105) -- Add referrerPolicy to Vimeo Video iframe to allow domain-restricted videos (by @davefarthing) + * [#10126](https://github.com/magento/magento2/pull/10126) -- Fix width & height mapping during image upload (by @ihor-sviziev) + * [#10140](https://github.com/magento/magento2/pull/10140) -- Update attribute vat_id frontend_label to make it translatable (by @JeroenVanLeusden) + * [#10149](https://github.com/magento/magento2/pull/10149) -- Fix wrong ACL for Developer Section (by @PascalBrouwers) + * [#10151](https://github.com/magento/magento2/pull/10151) -- Fix Widget saving non-XML entities to layout_update (by @tdgroot) + * [#9588](https://github.com/magento/magento2/pull/9588) -- Support controller src_type for head links (by @kassner) + * [#9904](https://github.com/magento/magento2/pull/9904) -- Fixed pointless exception in logs every time a category with image is saved (by @woutersamaey) + * [#10059](https://github.com/magento/magento2/pull/10059) -- Fix fetching quote item by id (by @mladenilic) + 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 +3990,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/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 2839ac5ee9d32..0000000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,36 +0,0 @@ -# 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”). - -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. - -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. - - -## 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. -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). - -## 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”. - -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. - -## Code of Conduct - -Please note that this project is released with a Contributor Code of Conduct. We expect you to agree to its terms when participating in this project. -The full text is available in the repository [Wiki](https://github.com/magento/magento2/wiki/Magento-Code-of-Conduct). 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/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 3ac68076d4353..0000000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,24 +0,0 @@ - - - -### Preconditions - - -1. -2. - -### Steps to reproduce - -1. -2. -3. - -### Expected result - -1. - -### Actual result - -1. [Screenshot, logs] - - diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index d1f01ba9f2640..0000000000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,20 +0,0 @@ - - -### Description - - -### Fixed Issues (if relevant) - -1. magento/magento2#: Issue title -2. ... - -### Manual testing scenarios - -1. ... -2. ... - -### 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) - - [ ] All automated tests passed successfully (all builds on Travis CI are green) diff --git a/README.md b/README.md index 9b1aa1b7b3e28..d64ac8061528f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -[![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.2-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)

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.2/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.2/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,28 +22,31 @@ 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. diff --git a/app/.htaccess b/app/.htaccess index 93169e4eb44ff..707c26b075e16 100644 --- a/app/.htaccess +++ b/app/.htaccess @@ -1,2 +1,8 @@ -Order deny,allow -Deny from all + + order allow,deny + deny from all + += 2.4> + Require all denied + + 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/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..f4cafa09c7e45 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,14 @@ 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..bec101fc27d48 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -31,11 +31,12 @@ 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..6088afbc2e1a4 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/System/Message/ListAction.php @@ -59,8 +59,10 @@ 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)); diff --git a/app/code/Magento/AdminNotification/Test/Mftf/LICENSE.txt b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/AdminNotification/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/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 afb820a2e6c93..ae1b8dc7d14ff 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -2,16 +2,16 @@ "name": "magento/module-admin-notification", "description": "N/A", "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "php": "~7.0.13|~7.1.0", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-media-storage": "100.2.*", - "magento/framework": "100.2.*", - "magento/module-ui": "100.2.*", + "magento/framework": "101.0.*", + "magento/module-ui": "101.0.*", "lib-libxml": "*" }, "type": "magento2-module", - "version": "100.2.0-dev", + "version": "100.2.4", "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/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/layout/adminhtml_notification_block.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml index 7778c1dd5ca98..c68313211c2e6 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml @@ -20,14 +20,14 @@ 0 - + Severity severity Magento\AdminNotification\Block\Grid\Renderer\Severity - + Date Added date_added @@ -37,14 +37,14 @@ col-date - + Message title Magento\AdminNotification\Block\Grid\Renderer\Notice - + Actions 0 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..818bcda1da65f 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -37,10 +37,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..3122a0a7ee648 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -79,6 +79,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 +103,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 +191,7 @@ protected function initTypeModels() * Export process * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function export() { @@ -213,6 +218,7 @@ public function export() break; } } + return $writer->getContents(); } @@ -255,70 +261,111 @@ 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)) { + $productLinkIds = array_map( + function (array $productData) { + return $productData[Store::DEFAULT_STORE_ID][$this->getProductEntityLinkField()]; + }, + $productsByStores + ); + $tierPricesData = $this->getTierPrices( + $productLinkIds, + ImportAdvancedPricing::TABLE_TIER_PRICE + ); + + $exportData = $this->correctExportData( + $productsByStores, + $tierPricesData + ); + if (!empty($exportData)) { + asort($exportData); } } - if ($exportData) { - $exportData = $this->correctExportData($exportData); - } - if (isset($exportData)) { - asort($exportData); - } } catch (\Exception $e) { $this->_logger->critical($e); } + return $exportData; } /** - * Correct export data. + * @param array $tierPriceData Tier price information. * - * @param array $exportData - * @return array - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @return array Formatted for export tier price information. */ - protected function correctExportData($exportData) + private function createExportRow(array $tierPriceData): array { - $customExportData = []; - foreach ($exportData as $key => $row) { - $exportRow = $this->templateExportData; - foreach ($exportRow as $keyTemplate => $valueTemplate) { - if (isset($row[$keyTemplate])) { - if (in_array($keyTemplate, $this->_priceWebsite)) { - $exportRow[$keyTemplate] = $this->_getWebsiteCode( - $row[$keyTemplate] - ); - } elseif (in_array($keyTemplate, $this->_priceCustomerGroup)) { - $exportRow[$keyTemplate] = $this->_getCustomerGroupById( - $row[$keyTemplate], - isset($row[ImportAdvancedPricing::VALUE_ALL_GROUPS]) - ? $row[ImportAdvancedPricing::VALUE_ALL_GROUPS] - : null - ); - unset($exportRow[ImportAdvancedPricing::VALUE_ALL_GROUPS]); - } elseif ($keyTemplate === ImportAdvancedPricing::COL_TIER_PRICE) { - $exportRow[$keyTemplate] = $row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] - ? $row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] - : $row[ImportAdvancedPricing::COL_TIER_PRICE]; - $exportRow[ImportAdvancedPricing::COL_TIER_PRICE_TYPE] - = $this->tierPriceTypeValue($row[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE]); - } else { - $exportRow[$keyTemplate] = $row[$keyTemplate]; - } + $exportRow = $this->templateExportData; + foreach (array_keys($exportRow) as $keyTemplate) { + if (array_key_exists($keyTemplate, $tierPriceData)) { + if (in_array($keyTemplate, $this->_priceWebsite)) { + $exportRow[$keyTemplate] = $this->_getWebsiteCode( + $tierPriceData[$keyTemplate] + ); + } elseif (in_array($keyTemplate, $this->_priceCustomerGroup)) { + $exportRow[$keyTemplate] = $this->_getCustomerGroupById( + $tierPriceData[$keyTemplate], + $tierPriceData[ImportAdvancedPricing::VALUE_ALL_GROUPS] + ); + unset($exportRow[ImportAdvancedPricing::VALUE_ALL_GROUPS]); + } elseif ($keyTemplate + === ImportAdvancedPricing::COL_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 { + $exportRow[$keyTemplate] = $tierPriceData[$keyTemplate]; } } + } + + return $exportRow; + } - $customExportData[$key] = $exportRow; - unset($exportRow); + /** + * Correct export data. + * + * @param array $productsData + * @param array $tierPricesData + * + * @return array + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + protected function correctExportData( + 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']; + } + unset($productData); + $linkedTierPricesData = []; + foreach ($tierPricesData as $tierPriceData) { + $sku = $productLinkIdToSkuMap[$tierPriceData['product_link_id']]; + $linkedTierPricesData[] = array_merge( + $tierPriceData, + [ImportAdvancedPricing::COL_SKU => $sku] + ); + } + unset($sku, $tierPriceData); + + $customExportData = []; + foreach ($linkedTierPricesData as $row) { + $customExportData[] = $this->createExportRow($row); } return $customExportData; @@ -327,12 +374,13 @@ 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; } @@ -340,54 +388,52 @@ private function tierPriceTypeValue($tierPricePercentage) /** * Get tier prices. * - * @param array $listSku + * @param string[] $productLinksIds * @param string $table * @return array|bool * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function getTierPrices(array $listSku, $table) + protected function getTierPrices(array $productLinksIds, $table) { + $exportFilter = null; + $price = null; if (isset($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP])) { $exportFilter = $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]; } + $productEntityLinkField = $this->getProductEntityLinkField(); + if ($table == ImportAdvancedPricing::TABLE_TIER_PRICE) { $selectFields = [ - ImportAdvancedPricing::COL_SKU => 'cpe.sku', - 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_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 (isset($exportFilter) && !empty($exportFilter)) { - $price = $exportFilter['tier_price']; - } - } - if ($listSku) { - if (isset($exportFilter) && !empty($exportFilter)) { - $date = $exportFilter[\Magento\Catalog\Model\Category::KEY_UPDATED_AT]; - if (isset($date[0]) && !empty($date[0])) { - $updatedAtFrom = $this->_localeDate->date($date[0], null, false)->format('Y-m-d H:i:s'); - } - if (isset($date[1]) && !empty($date[1])) { - $updatedAtTo = $this->_localeDate->date($date[1], null, false)->format('Y-m-d H:i:s'); + if ($exportFilter) { + if (array_key_exists('tier_price', $exportFilter)) { + $price = $exportFilter['tier_price']; } } + } else { + throw new \InvalidArgumentException('Proper table name needed'); + } + + if ($productLinksIds) { try { - $productEntityLinkField = $this->getProductEntityLinkField(); $select = $this->_connection->select() ->from( - ['cpe' => $this->_resource->getTableName('catalog_product_entity')], - $selectFields - ) - ->joinInner( ['ap' => $this->_resource->getTableName($table)], - 'ap.' . $productEntityLinkField . ' = cpe.' . $productEntityLinkField, - [] + $selectFields ) - ->where('cpe.entity_id IN (?)', $listSku); + ->where( + 'ap.'.$productEntityLinkField.' IN (?)', + $productLinksIds + ); if (isset($price[0]) && !empty($price[0])) { $select->where('ap.value >= ?', $price[0]); @@ -398,18 +444,16 @@ protected function getTierPrices(array $listSku, $table) if (isset($price[0]) && !empty($price[0]) || isset($price[1]) && !empty($price[1])) { $select->orWhere('ap.percentage_value IS NOT NULL'); } - if (isset($updatedAtFrom) && !empty($updatedAtFrom)) { - $select->where('cpe.updated_at >= ?', $updatedAtFrom); - } - if (isset($updatedAtTo) && !empty($updatedAtTo)) { - $select->where('cpe.updated_at <= ?', $updatedAtTo); - } + $exportData = $this->_connection->fetchAll($select); } catch (\Exception $e) { return false; } + + return $exportData; + } else { + return false; } - return $exportData; } /** @@ -417,36 +461,50 @@ protected function getTierPrices(array $listSku, $table) * * @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 * * @param int $customerGroupId - * @param null $allGroups + * @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 $customerGroupId, + int $allGroups = 0 + ): string { + if ($allGroups !== 0) { return ImportAdvancedPricing::VALUE_ALL_GROUPS; } else { - return $this->_groupRepository->getById($customerGroupId)->getCode(); + return $this->_groupRepository + ->getById($customerGroupId) + ->getCode(); } } diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 23829d3725119..754f5fd6c8c20 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]) ]; } } @@ -619,6 +618,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/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE.txt b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000000000..2b7359b7dfcb4 --- /dev/null +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/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..c927aad6ac714 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php @@ -213,7 +213,7 @@ protected function setUp() '_getCustomerGroupById', 'correctExportData' ]); - $this->advancedPricing = $this->getMockbuilder( + $this->advancedPricing = $this->getMockBuilder( \Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing::class ) ->setMethods($mockMethods) @@ -347,6 +347,7 @@ protected function tearDown() * @param $object * @param $property * @return mixed + * @throws \ReflectionException */ protected function getPropertyValue($object, $property) { @@ -362,6 +363,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..15a3ae3a22d4a 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) { @@ -981,7 +1008,8 @@ protected function setPropertyValue(&$object, $property, $value) * @param string $method * @param array $args * - * @return mixed the method result. + * @return mixed + * @throws \ReflectionException */ private function invokeMethod($object, $method, $args = []) { @@ -998,6 +1026,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 228464ecd6304..458827b9ab18a 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -2,18 +2,18 @@ "name": "magento/module-advanced-pricing-import-export", "description": "N/A", "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/module-catalog": "101.1.*", + "php": "~7.0.13|~7.1.0", + "magento/module-catalog": "102.0.*", "magento/module-catalog-inventory": "100.2.*", - "magento/module-eav": "100.2.*", + "magento/module-eav": "101.0.*", "magento/module-import-export": "100.2.*", "magento/module-catalog-import-export": "100.2.*", - "magento/module-customer": "100.2.*", + "magento/module-customer": "101.0.*", "magento/module-store": "100.2.*", - "magento/framework": "100.2.*" + "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.0-dev", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Analytics/Api/Data/LinkInterface.php b/app/code/Magento/Analytics/Api/Data/LinkInterface.php new file mode 100644 index 0000000000000..6597dff868b9f --- /dev/null +++ b/app/code/Magento/Analytics/Api/Data/LinkInterface.php @@ -0,0 +1,24 @@ +' . $element->getLabel() . ''; + $html .= '
' . $element->getComment() . '
'; + return $this->decorateRowHtml($element, $html); + } + + /** + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param string $html + * @return string + */ + private function decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) + { + return sprintf( + '
%s
', + $element->getHtmlId(), + $html + ); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php new file mode 100644 index 0000000000000..34f2b7d53d9be --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php @@ -0,0 +1,53 @@ +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 + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $timeZoneCode = $this->_localeDate->getConfigTimezone(); + $locale = $this->localeResolver->getLocale(); + $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode) + ->getDisplayName(false, \IntlTimeZone::DISPLAY_LONG, $locale); + $element->setData( + 'comment', + sprintf("%s (%s)", $getLongTimeZoneName, $timeZoneCode) + ); + return parent::render($element); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php new file mode 100644 index 0000000000000..c09213c7f009d --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php @@ -0,0 +1,64 @@ +subscriptionStatusProvider = $labelStatusProvider; + } + + /** + * Add Subscription status to comment + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $element->setData( + 'comment', + $this->prepareLabelValue() + ); + return parent::render($element); + } + + /** + * Prepare label for subscription status + * + * @return string + */ + private function prepareLabelValue() + { + return __('Subscription status') . ': ' . __($this->subscriptionStatusProvider->getStatus()); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php new file mode 100644 index 0000000000000..99606e10f99d9 --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php @@ -0,0 +1,41 @@ +' . $element->getHint() . ''; + $html .= '
' . $element->getComment() . '
'; + return $this->decorateRowHtml($element, $html); + } + + /** + * Decorates row HTML for custom element style + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param string $html + * @return string + */ + private function decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) + { + $rowHtml = sprintf('%s', $html); + $rowHtml .= sprintf( + '%s%s', + $element->getHtmlId(), + $element->getLabelHtml($element->getHtmlId(), "[WEBSITE]"), + $element->getElementHtml() + ); + return $rowHtml; + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php new file mode 100644 index 0000000000000..a90a971cf41b4 --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php @@ -0,0 +1,64 @@ +config = $config; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::bi_essentials'); + } + + /** + * Provides link to BI Essentials signup + * + * @return \Magento\Framework\Controller\AbstractResult + */ + public function execute() + { + return $this->resultRedirectFactory->create()->setUrl( + $this->config->getValue($this->urlBIEssentialsConfigPath) + ); + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php new file mode 100644 index 0000000000000..1b0e5c92420de --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php @@ -0,0 +1,75 @@ +reportUrlProvider = $reportUrlProvider; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller. + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Redirect to resource with reports. + * + * @return Redirect $resultRedirect + */ + public function execute() + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + try { + $resultRedirect->setUrl($this->reportUrlProvider->getUrl()); + } catch (SubscriptionUpdateException $e) { + $this->getMessageManager()->addNoticeMessage($e->getMessage()); + $resultRedirect->setPath('adminhtml'); + } catch (LocalizedException $e) { + $this->getMessageManager()->addExceptionMessage($e, $e->getMessage()); + $resultRedirect->setPath('adminhtml'); + } catch (\Exception $e) { + $this->getMessageManager()->addExceptionMessage( + $e, + __('Sorry, there has been an error processing your request. Please try again later.') + ); + $resultRedirect->setPath('adminhtml'); + } + + return $resultRedirect; + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php new file mode 100644 index 0000000000000..122cf74123cc9 --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php @@ -0,0 +1,73 @@ +subscriptionHandler = $subscriptionHandler; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Retry process of subscription. + * + * @return Redirect + */ + public function execute() + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + try { + $resultRedirect->setPath('adminhtml'); + $this->subscriptionHandler->processEnabled(); + } catch (LocalizedException $e) { + $this->getMessageManager()->addExceptionMessage($e, $e->getMessage()); + } catch (\Exception $e) { + $this->getMessageManager()->addExceptionMessage( + $e, + __('Sorry, there has been an error processing your request. Please try again later.') + ); + } + + return $resultRedirect; + } +} diff --git a/app/code/Magento/Analytics/Cron/CollectData.php b/app/code/Magento/Analytics/Cron/CollectData.php new file mode 100644 index 0000000000000..ff0b3e4f67638 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/CollectData.php @@ -0,0 +1,53 @@ +exportDataHandler = $exportDataHandler; + $this->subscriptionStatus = $subscriptionStatus; + } + + /** + * @return bool + */ + public function execute() + { + if ($this->subscriptionStatus->getStatus() === SubscriptionStatusProvider::ENABLED) { + $this->exportDataHandler->prepareExportData(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Cron/SignUp.php b/app/code/Magento/Analytics/Cron/SignUp.php new file mode 100644 index 0000000000000..c17b9b8c381c3 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/SignUp.php @@ -0,0 +1,101 @@ +connector = $connector; + $this->configWriter = $configWriter; + $this->flagManager = $flagManager; + $this->reinitableConfig = $reinitableConfig; + } + + /** + * Execute scheduled subscription operation + * In case of failure writes message to notifications inbox + * + * @return bool + */ + public function execute() + { + $attemptsCount = $this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + + if (($attemptsCount === null) || ($attemptsCount <= 0)) { + $this->deleteAnalyticsCronExpr(); + $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + return false; + } + + $attemptsCount -= 1; + $this->flagManager->saveFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); + $signUpResult = $this->connector->execute('signUp'); + if ($signUpResult === false) { + return false; + } + + $this->deleteAnalyticsCronExpr(); + $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + return true; + } + + /** + * Delete cron schedule setting into config. + * + * Delete cron schedule setting for subscription handler into config and + * re-initialize config cache to avoid auto-generate new schedule items. + * + * @return bool + */ + private function deleteAnalyticsCronExpr() + { + $this->configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); + $this->reinitableConfig->reinit(); + return true; + } +} diff --git a/app/code/Magento/Analytics/Cron/Update.php b/app/code/Magento/Analytics/Cron/Update.php new file mode 100644 index 0000000000000..9062a7bac7551 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/Update.php @@ -0,0 +1,92 @@ +connector = $connector; + $this->configWriter = $configWriter; + $this->reinitableConfig = $reinitableConfig; + $this->flagManager = $flagManager; + $this->analyticsToken = $analyticsToken; + } + + /** + * Execute scheduled update operation + * + * @return bool + */ + public function execute() + { + $result = false; + $attemptsCount = $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + + if ($attemptsCount) { + $attemptsCount -= 1; + $result = $this->connector->execute('update'); + } + + if ($result || ($attemptsCount <= 0) || (!$this->analyticsToken->isTokenExist())) { + $this->flagManager + ->deleteFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + $this->flagManager->deleteFlag(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); + $this->configWriter->delete(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); + $this->reinitableConfig->reinit(); + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/LICENSE.txt b/app/code/Magento/Analytics/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Analytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Analytics/LICENSE_AFL.txt b/app/code/Magento/Analytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Analytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/Analytics/Model/AnalyticsToken.php b/app/code/Magento/Analytics/Model/AnalyticsToken.php new file mode 100644 index 0000000000000..ccec4d1bbe958 --- /dev/null +++ b/app/code/Magento/Analytics/Model/AnalyticsToken.php @@ -0,0 +1,92 @@ +reinitableConfig = $reinitableConfig; + $this->config = $config; + $this->configWriter = $configWriter; + } + + /** + * Get Magento BI token value. + * + * @return string|null + */ + public function getToken() + { + return $this->config->getValue($this->tokenPath); + } + + /** + * Stores Magento BI token value. + * + * @param string $value + * + * @return bool + */ + public function storeToken($value) + { + $this->configWriter->save($this->tokenPath, $value); + $this->reinitableConfig->reinit(); + + return true; + } + + /** + * Check Magento BI token value exist. + * + * @return bool + */ + public function isTokenExist() + { + return (bool)$this->getToken(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config.php b/app/code/Magento/Analytics/Model/Config.php new file mode 100644 index 0000000000000..ba508187b4b9f --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config.php @@ -0,0 +1,40 @@ +data = $data; + } + + /** + * Get config value by key. + * + * @param string|null $key + * @param string|null $default + * @return array + */ + public function get($key = null, $default = null) + { + return $this->data->get($key, $default); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php b/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php new file mode 100644 index 0000000000000..6e6f008d49f7e --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php @@ -0,0 +1,107 @@ +analyticsToken = $analyticsToken; + $this->flagManager = $flagManager; + $this->reinitableConfig = $reinitableConfig; + $this->configWriter = $configWriter; + } + + /** + * Activate process of subscription update handling. + * + * @param string $url + * @return bool + */ + public function processUrlUpdate(string $url) + { + if ($this->analyticsToken->isTokenExist()) { + if (!$this->flagManager->getFlagData(self::PREVIOUS_BASE_URL_FLAG_CODE)) { + $this->flagManager->saveFlag(self::PREVIOUS_BASE_URL_FLAG_CODE, $url); + } + + $this->flagManager + ->saveFlag(self::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue); + $this->configWriter->save(self::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfig->reinit(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php new file mode 100644 index 0000000000000..e26ad01fc74bf --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php @@ -0,0 +1,91 @@ +configWriter = $configWriter; + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + } + + /** + * {@inheritdoc} + * + * {@inheritdoc}. Set schedule setting for cron. + * + * @return Value + */ + 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')); + } + + $cronExprArray = [ + $time['min'], # Minute + $time['hour'], # Hour + '*', # Day of the Month + '*', # Month of the Year + '*', # Day of the Week + ]; + + $cronExprString = join(' ', $cronExprArray); + + try { + $this->configWriter->save(self::CRON_SCHEDULE_PATH, $cronExprString); + } catch (\Exception $e) { + $this->_logger->error($e->getMessage()); + throw new LocalizedException(__('Cron settings can\'t be saved')); + } + + return parent::afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php new file mode 100644 index 0000000000000..ac97f2a843e61 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php @@ -0,0 +1,84 @@ +subscriptionHandler = $subscriptionHandler; + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + } + + /** + * Add additional handling after config value was saved. + * + * @return Value + * @throws LocalizedException + */ + public function afterSave() + { + try { + if ($this->isValueChanged()) { + $enabled = $this->getData('value'); + + if ($enabled) { + $this->subscriptionHandler->processEnabled(); + } else { + $this->subscriptionHandler->processDisabled(); + } + } + } catch (\Exception $e) { + $this->_logger->error($e->getMessage()); + throw new LocalizedException(__('There was an error save new configuration value.')); + } + + return parent::afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php b/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php new file mode 100644 index 0000000000000..4b125949948c6 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php @@ -0,0 +1,172 @@ +configWriter = $configWriter; + $this->flagManager = $flagManager; + $this->analyticsToken = $analyticsToken; + $this->reinitableConfig = $reinitableConfig; + } + + /** + * Processing of activation MBI subscription. + * + * Activate process of subscription handling if Analytics token is not received. + * + * @return bool + */ + public function processEnabled() + { + if (!$this->analyticsToken->isTokenExist()) { + $this->setCronSchedule(); + $this->setAttemptsFlag(); + $this->reinitableConfig->reinit(); + } + + return true; + } + + /** + * Set cron schedule setting into config for activation of subscription process. + * + * @return bool + */ + private function setCronSchedule() + { + $this->configWriter->save(self::CRON_STRING_PATH, join(' ', self::CRON_EXPR_ARRAY)); + return true; + } + + /** + * Set flag as reserve counter of attempts subscription operation. + * + * @return bool + */ + private function setAttemptsFlag() + { + return $this->flagManager + ->saveFlag(self::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue); + } + + /** + * Processing of deactivation MBI subscription. + * + * Disable data collection + * and interrupt subscription handling if Analytics token is not received. + * + * @return bool + */ + public function processDisabled() + { + $this->disableCollectionData(); + + if (!$this->analyticsToken->isTokenExist()) { + $this->unsetAttemptsFlag(); + } + + return true; + } + + /** + * Unset flag of attempts subscription operation. + * + * @return bool + */ + private function unsetAttemptsFlag() + { + return $this->flagManager + ->deleteFlag(self::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + } + + /** + * Unset schedule of collection data cron. + * + * @return bool + */ + private function disableCollectionData() + { + $this->configWriter->delete(CollectionTime::CRON_SCHEDULE_PATH); + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php b/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php new file mode 100644 index 0000000000000..1aabbb91ddf87 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php @@ -0,0 +1,32 @@ +getValue())) { + throw new LocalizedException(__('Please select a vertical.')); + } + + return $this; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Mapper.php b/app/code/Magento/Analytics/Model/Config/Mapper.php new file mode 100644 index 0000000000000..504690b8e4763 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Mapper.php @@ -0,0 +1,66 @@ + [ + * 'name' => 'file_name', + * 'providers' => [ + * 'reportProvider' => [ + * 'name' => 'report_provider_name', + * 'class' => 'Magento\Analytics\ReportXml\ReportProvider', + * 'parameters' =>[ + * 'name' => 'report_name', + * ], + * ], + * 'customProvider' => [ + * 'name' => 'custom_provider_name', + * 'class' => 'Magento\Analytics\Model\CustomProvider', + * ], + * ], + * ] + * ]; + */ + public function execute($configData) + { + if (!isset($configData['config'][0]['file'])) { + return []; + } + + $files = []; + foreach ($configData['config'][0]['file'] as $fileData) { + /** just one set of providers is allowed by xsd */ + $providers = reset($fileData['providers']); + foreach ($providers as $providerType => $providerDataSet) { + /** just one set of provider data is allowed by xsd */ + $providerData = reset($providerDataSet); + /** just one set of parameters is allowed by xsd */ + $providerData['parameters'] = !empty($providerData['parameters']) + ? reset($providerData['parameters']) + : []; + $providerData['parameters'] = array_map( + 'reset', + $providerData['parameters'] + ); + $providers[$providerType] = $providerData; + } + $files[$fileData['name']] = $fileData; + $files[$fileData['name']]['providers'] = $providers; + } + return $files; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Reader.php b/app/code/Magento/Analytics/Model/Config/Reader.php new file mode 100644 index 0000000000000..8980e31627717 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Reader.php @@ -0,0 +1,52 @@ +mapper = $mapper; + $this->readers = $readers; + } + + /** + * Read configuration scope. + * + * @param string|null $scope + * @return array + */ + public function read($scope = null) + { + $data = []; + foreach ($this->readers as $reader) { + $data = array_merge_recursive($data, $reader->read($scope)); + } + + return $this->mapper->execute($data); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Source/Vertical.php b/app/code/Magento/Analytics/Model/Config/Source/Vertical.php new file mode 100644 index 0000000000000..c9d9582ea7c7a --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Source/Vertical.php @@ -0,0 +1,51 @@ +verticals = $verticals; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + $result = [ + ['value' => '', 'label' => __('--Please Select--')] + ]; + + foreach ($this->verticals as $vertical) { + $result[] = ['value' => $vertical, 'label' => __($vertical)]; + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/ConfigInterface.php b/app/code/Magento/Analytics/Model/ConfigInterface.php new file mode 100644 index 0000000000000..caaa2e100c1c7 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ConfigInterface.php @@ -0,0 +1,22 @@ + 'command_class_name'. + * + * The list may be configured in each module via '/etc/di.xml'. + * + * @var string[] + */ + private $commands; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param array $commands + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + array $commands, + ObjectManagerInterface $objectManager + ) { + $this->commands = $commands; + $this->objectManager = $objectManager; + } + + /** + * Executes a command in accordance with the given name. + * + * @param string $commandName + * @return bool + * @throws NotFoundException if the command is not found. + */ + public function execute($commandName) + { + if (!array_key_exists($commandName, $this->commands)) { + throw new NotFoundException(__('Command was not found.')); + } + + /** @var \Magento\Analytics\Model\Connector\CommandInterface $command */ + $command = $this->objectManager->create($this->commands[$commandName]); + + return $command->execute(); + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/CommandInterface.php b/app/code/Magento/Analytics/Model/Connector/CommandInterface.php new file mode 100644 index 0000000000000..7a8774fe3dba9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/CommandInterface.php @@ -0,0 +1,21 @@ +curlFactory = $curlFactory; + $this->responseFactory = $responseFactory; + $this->converter = $converter; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function request($method, $url, array $body = [], array $headers = [], $version = '1.1') + { + $response = new \Zend_Http_Response(0, []); + + try { + $curl = $this->curlFactory->create(); + $headers = $this->applyContentTypeHeaderFromConverter($headers); + + $curl->write($method, $url, $version, $headers, $this->converter->toBody($body)); + + $result = $curl->read(); + + if ($curl->getErrno()) { + $this->logger->critical( + new \Exception( + sprintf( + 'MBI service CURL connection error #%s: %s', + $curl->getErrno(), + $curl->getError() + ) + ) + ); + + return $response; + } + + $response = $this->responseFactory->create($result); + } catch (\Exception $e) { + $this->logger->critical($e); + } + + return $response; + } + + /** + * @param array $headers + * + * @return array + */ + private function applyContentTypeHeaderFromConverter(array $headers) + { + $contentTypeHeaderKey = array_search($this->converter->getContentTypeHeader(), $headers); + if ($contentTypeHeaderKey === false) { + $headers[] = $this->converter->getContentTypeHeader(); + } + + return $headers; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php new file mode 100644 index 0000000000000..a1e1f057684f6 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php @@ -0,0 +1,29 @@ +converter = $converter; + $this->responseHandlers = $responseHandlers; + } + + /** + * @param \Zend_Http_Response $response + * + * @return bool|string + */ + public function getResult(\Zend_Http_Response $response) + { + $result = false; + $responseBody = $this->converter->fromBody($response->getBody()); + if (array_key_exists($response->getStatus(), $this->responseHandlers)) { + $result = $this->responseHandlers[$response->getStatus()]->handleResponse($responseBody); + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php new file mode 100644 index 0000000000000..f1a8ea6460f9d --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php @@ -0,0 +1,93 @@ +analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->responseResolver = $responseResolver; + $this->logger = $logger; + } + + /** + * Notify MBI about that data collection was finished + * + * @return bool + */ + public function execute() + { + $result = false; + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->notifyDataChangedUrlPath), + [ + "access-token" => $this->analyticsToken->getToken(), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + $result = $this->responseResolver->getResult($response); + } + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php new file mode 100644 index 0000000000000..dfa283e10d070 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php @@ -0,0 +1,115 @@ +analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->responseResolver = $responseResolver; + $this->logger = $logger; + } + + /** + * Performs obtaining of an OTP from the MBI service. + * + * Returns received OTP or FALSE in case of failure. + * + * @return string|false + */ + public function call() + { + $result = false; + + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->otpUrlConfigPath), + [ + "access-token" => $this->analyticsToken->getToken(), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + + $result = $this->responseResolver->getResult($response); + 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.' + ) + ); + } + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php new file mode 100644 index 0000000000000..d9a672e81f43d --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php @@ -0,0 +1,24 @@ +analyticsToken = $analyticsToken; + $this->subscriptionHandler = $subscriptionHandler; + $this->subscriptionStatusProvider = $subscriptionStatusProvider; + } + + /** + * @inheritdoc + */ + public function handleResponse(array $responseBody) + { + if ($this->subscriptionStatusProvider->getStatus() === SubscriptionStatusProvider::ENABLED) { + $this->analyticsToken->storeToken(null); + $this->subscriptionHandler->processEnabled(); + } + return false; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php new file mode 100644 index 0000000000000..b2261e418abc7 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php @@ -0,0 +1,51 @@ +analyticsToken = $analyticsToken; + $this->converter = $converter; + } + + /** + * @inheritdoc + */ + public function handleResponse(array $body) + { + if (isset($body['access-token']) && !empty($body['access-token'])) { + $this->analyticsToken->storeToken($body['access-token']); + return $body['access-token']; + } + + return false; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php new file mode 100644 index 0000000000000..73fc575ae2821 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php @@ -0,0 +1,24 @@ +analyticsToken = $analyticsToken; + $this->integrationManager = $integrationManager; + $this->config = $config; + $this->httpClient = $httpClient; + $this->logger = $logger; + $this->responseResolver = $responseResolver; + } + + /** + * Executes signUp command + * + * During this call Magento generates or retrieves access token for the integration user + * In case successful generation Magento activates user and sends access token to MA + * As the response, Magento receives a token to MA + * Magento stores this token in System Configuration + * + * This method returns true in case of success + * + * @return bool + */ + public function execute() + { + $result = false; + $integrationToken = $this->integrationManager->generateToken(); + if ($integrationToken) { + $this->integrationManager->activateIntegration(); + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->signUpUrlPath), + [ + "token" => $integrationToken->getData('token'), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + + $result = $this->responseResolver->getResult($response); + 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.' + ) + ); + } + } + + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php new file mode 100644 index 0000000000000..8f05f1107e87e --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php @@ -0,0 +1,114 @@ +analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->logger = $logger; + $this->flagManager = $flagManager; + $this->responseResolver = $responseResolver; + } + + /** + * Executes update request to MBI api in case store url was changed + * + * @return bool + */ + public function execute() + { + $result = false; + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::PUT, + $this->config->getValue($this->updateUrlPath), + [ + "url" => $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE), + "new-url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + "access-token" => $this->analyticsToken->getToken(), + ] + ); + $result = $this->responseResolver->getResult($response); + 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.' + ) + ); + } + } + + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Cryptographer.php b/app/code/Magento/Analytics/Model/Cryptographer.php new file mode 100644 index 0000000000000..7659d44801091 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Cryptographer.php @@ -0,0 +1,135 @@ +analyticsToken = $analyticsToken; + $this->encodedContextFactory = $encodedContextFactory; + } + + /** + * Encrypt input data. + * + * @param string $source + * @return EncodedContext + * @throws LocalizedException + */ + public function encode($source) + { + if (!is_string($source)) { + try { + $source = (string)$source; + } catch (\Exception $e) { + throw new LocalizedException(__('Input data must be string or convertible into string.')); + } + } elseif (!$source) { + throw new LocalizedException(__('Input data must be non-empty string.')); + } + if (!$this->validateCipherMethod($this->cipherMethod)) { + throw new LocalizedException(__('Not valid cipher method.')); + } + $initializationVector = $this->getInitializationVector(); + + $encodedContext = $this->encodedContextFactory->create([ + 'content' => openssl_encrypt( + $source, + $this->cipherMethod, + $this->getKey(), + OPENSSL_RAW_DATA, + $initializationVector + ), + 'initializationVector' => $initializationVector, + ]); + + return $encodedContext; + } + + /** + * Return key for encryption. + * + * @return string + * @throws LocalizedException + */ + private function getKey() + { + $token = $this->analyticsToken->getToken(); + if (!$token) { + throw new LocalizedException(__('Encryption key can\'t be empty.')); + } + return hash('sha256', $token); + } + + /** + * Return established cipher method. + * + * @return string + */ + private function getCipherMethod() + { + return $this->cipherMethod; + } + + /** + * Return each time generated random initialization vector which depends on the cipher method. + * + * @return string + */ + private function getInitializationVector() + { + $ivSize = openssl_cipher_iv_length($this->getCipherMethod()); + return openssl_random_pseudo_bytes($ivSize); + } + + /** + * Check that cipher method is allowed for encryption. + * + * @param string $cipherMethod + * @return bool + */ + private function validateCipherMethod($cipherMethod) + { + $methods = array_map( + 'strtolower', + openssl_get_cipher_methods() + ); + $cipherMethod = strtolower($cipherMethod); + + return (false !== array_search($cipherMethod, $methods)); + } +} diff --git a/app/code/Magento/Analytics/Model/EncodedContext.php b/app/code/Magento/Analytics/Model/EncodedContext.php new file mode 100644 index 0000000000000..5fb2d0c15aef7 --- /dev/null +++ b/app/code/Magento/Analytics/Model/EncodedContext.php @@ -0,0 +1,52 @@ +content = $content; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php b/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php new file mode 100644 index 0000000000000..5d127037afea9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php @@ -0,0 +1,17 @@ +filesystem = $filesystem; + $this->archive = $archive; + $this->reportWriter = $reportWriter; + $this->cryptographer = $cryptographer; + $this->fileRecorder = $fileRecorder; + } + + /** + * @inheritdoc + */ + public function prepareExportData() + { + try { + $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); + + $this->prepareDirectory($tmpDirectory, $this->getTmpFilesDirRelativePath()); + $this->reportWriter->write($tmpDirectory, $this->getTmpFilesDirRelativePath()); + + $tmpFilesDirectoryAbsolutePath = $this->validateSource($tmpDirectory, $this->getTmpFilesDirRelativePath()); + $archiveAbsolutePath = $this->prepareFileDirectory($tmpDirectory, $this->getArchiveRelativePath()); + $this->pack( + $tmpFilesDirectoryAbsolutePath, + $archiveAbsolutePath + ); + + $this->validateSource($tmpDirectory, $this->getArchiveRelativePath()); + $this->fileRecorder->recordNewFile( + $this->cryptographer->encode($tmpDirectory->readFile($this->getArchiveRelativePath())) + ); + } finally { + $tmpDirectory->delete($this->getTmpFilesDirRelativePath()); + $tmpDirectory->delete($this->getArchiveRelativePath()); + } + + return true; + } + + /** + * Return relative path to a directory for temporary files with reports data. + * + * @return string + */ + private function getTmpFilesDirRelativePath() + { + return $this->subdirectoryPath . 'tmp/'; + } + + /** + * Return relative path to a directory for an archive. + * + * @return string + */ + private function getArchiveRelativePath() + { + return $this->subdirectoryPath . $this->archiveName; + } + + /** + * Clean up a directory. + * + * @param WriteInterface $directory + * @param string $path + * @return string + */ + private function prepareDirectory(WriteInterface $directory, $path) + { + $directory->delete($path); + + return $directory->getAbsolutePath($path); + } + + /** + * Remove a file and a create parent directory a file. + * + * @param WriteInterface $directory + * @param string $path + * @return string + */ + private function prepareFileDirectory(WriteInterface $directory, $path) + { + $directory->delete($path); + if (dirname($path) !== '.') { + $directory->create(dirname($path)); + } + + return $directory->getAbsolutePath($path); + } + + /** + * Packing data into an archive. + * + * @param string $source + * @param string $destination + * @return bool + */ + private function pack($source, $destination) + { + $this->archive->pack( + $source, + $destination, + is_dir($source) ?: false + ); + + return true; + } + + /** + * Validate that data source exist. + * + * Return absolute path in a validated data source. + * + * @param WriteInterface $directory + * @param string $path + * @return string + * @throws LocalizedException If source is not exist. + */ + private function validateSource(WriteInterface $directory, $path) + { + if (!$directory->isExist($path)) { + throw new LocalizedException(__('Source "%1" is not exist', $directory->getAbsolutePath($path))); + } + + return $directory->getAbsolutePath($path); + } +} diff --git a/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php b/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php new file mode 100644 index 0000000000000..65efb33659c89 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php @@ -0,0 +1,19 @@ +exportDataHandler = $exportDataHandler; + $this->analyticsConnector = $connector; + } + + /** + * {@inheritdoc} + * Execute notification command. + * + * @return bool + */ + public function prepareExportData() + { + $result = $this->exportDataHandler->prepareExportData(); + $this->analyticsConnector->execute('notifyDataChanged'); + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/FileInfo.php b/app/code/Magento/Analytics/Model/FileInfo.php new file mode 100644 index 0000000000000..19bdaf21b2a20 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfo.php @@ -0,0 +1,52 @@ +path = $path; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/FileInfoManager.php b/app/code/Magento/Analytics/Model/FileInfoManager.php new file mode 100644 index 0000000000000..e37700e665420 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfoManager.php @@ -0,0 +1,123 @@ +flagManager = $flagManager; + $this->fileInfoFactory = $fileInfoFactory; + } + + /** + * Save FileInfo object. + * + * @param FileInfo $fileInfo + * @return bool + * @throws LocalizedException + */ + public function save(FileInfo $fileInfo) + { + $parameters = []; + $parameters['initializationVector'] = $fileInfo->getInitializationVector(); + $parameters['path'] = $fileInfo->getPath(); + + $emptyParameters = array_diff($parameters, array_filter($parameters)); + if ($emptyParameters) { + throw new LocalizedException( + __('These arguments can\'t be empty "%1"', implode(', ', array_keys($emptyParameters))) + ); + } + + foreach ($this->encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = $this->encodeValue($parameters[$encodedParameter]); + } + + $this->flagManager->saveFlag($this->flagCode, $parameters); + + return true; + } + + /** + * Load FileInfo object. + * + * @return FileInfo + */ + public function load() + { + $parameters = $this->flagManager->getFlagData($this->flagCode) ?: []; + + $encodedParameters = array_intersect($this->encodedParameters, array_keys($parameters)); + foreach ($encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = $this->decodeValue($parameters[$encodedParameter]); + } + + $fileInfo = $this->fileInfoFactory->create($parameters); + + return $fileInfo; + } + + /** + * Encode value. + * + * @param string $value + * @return string + */ + private function encodeValue($value) + { + return base64_encode($value); + } + + /** + * Decode value. + * + * @param string $value + * @return string + */ + private function decodeValue($value) + { + return base64_decode($value); + } +} diff --git a/app/code/Magento/Analytics/Model/FileRecorder.php b/app/code/Magento/Analytics/Model/FileRecorder.php new file mode 100644 index 0000000000000..70438a98d56f1 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileRecorder.php @@ -0,0 +1,136 @@ +fileInfoManager = $fileInfoManager; + $this->fileInfoFactory = $fileInfoFactory; + $this->filesystem = $filesystem; + } + + /** + * Save new encrypted file, register it and remove old registered file. + * + * @param EncodedContext $encodedContext + * @return bool + */ + public function recordNewFile(EncodedContext $encodedContext) + { + $directory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + + $fileRelativePath = $this->getFileRelativePath(); + $directory->writeFile($fileRelativePath, $encodedContext->getContent()); + + $fileInfo = $this->fileInfoManager->load(); + $this->registerFile($encodedContext, $fileRelativePath); + $this->removeOldFile($fileInfo, $directory); + + return true; + } + + /** + * Return relative path to encoded file. + * + * @return string + */ + private function getFileRelativePath() + { + return $this->fileSubdirectoryPath . hash('sha256', time()) + . '/' . $this->encodedFileName; + } + + /** + * Register encoded file. + * + * @param EncodedContext $encodedContext + * @param string $fileRelativePath + * @return bool + */ + private function registerFile(EncodedContext $encodedContext, $fileRelativePath) + { + $newFileInfo = $this->fileInfoFactory->create( + [ + 'path' => $fileRelativePath, + 'initializationVector' => $encodedContext->getInitializationVector(), + ] + ); + $this->fileInfoManager->save($newFileInfo); + + return true; + } + + /** + * Remove previously registered file. + * + * @param FileInfo $fileInfo + * @param WriteInterface $directory + * @return bool + */ + private function removeOldFile(FileInfo $fileInfo, WriteInterface $directory) + { + if (!$fileInfo->getPath()) { + return true; + } + + $directory->delete($fileInfo->getPath()); + + $directoryName = dirname($fileInfo->getPath()); + if ($directoryName !== '.') { + $directory->delete($directoryName); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/IntegrationManager.php b/app/code/Magento/Analytics/Model/IntegrationManager.php new file mode 100644 index 0000000000000..61a40a955e026 --- /dev/null +++ b/app/code/Magento/Analytics/Model/IntegrationManager.php @@ -0,0 +1,126 @@ +integrationService = $integrationService; + $this->config = $config; + $this->oauthService = $oauthService; + } + + /** + * Activate predefined integration user + * + * @return bool + * @throws NoSuchEntityException + */ + public function activateIntegration() + { + $integration = $this->integrationService->findByName( + $this->config->getConfigDataValue('analytics/integration_name') + ); + if (!$integration->getId()) { + throw new NoSuchEntityException(__('Cannot find predefined integration user!')); + } + $integrationData = $this->getIntegrationData(Integration::STATUS_ACTIVE); + $integrationData['integration_id'] = $integration->getId(); + $this->integrationService->update($integrationData); + return true; + } + + /** + * This method execute Generate Token command and enable integration + * + * @return bool|\Magento\Integration\Model\Oauth\Token + */ + public function generateToken() + { + $consumerId = $this->generateIntegration()->getConsumerId(); + $accessToken = $this->oauthService->getAccessToken($consumerId); + if (!$accessToken && $this->oauthService->createAccessToken($consumerId, true)) { + $accessToken = $this->oauthService->getAccessToken($consumerId); + } + return $accessToken; + } + + /** + * Returns consumer Id for MA integration user + * + * @return \Magento\Integration\Model\Integration + */ + private function generateIntegration() + { + $integration = $this->integrationService->findByName( + $this->config->getConfigDataValue('analytics/integration_name') + ); + if (!$integration->getId()) { + $integration = $this->integrationService->create($this->getIntegrationData()); + } + return $integration; + } + + /** + * Returns default attributes for MA integration user + * + * @param int $status + * @return array + */ + private function getIntegrationData($status = Integration::STATUS_INACTIVE) + { + $integrationData = [ + 'name' => $this->config->getConfigDataValue('analytics/integration_name'), + 'status' => $status, + 'all_resources' => false, + 'resource' => [ + 'Magento_Analytics::analytics', + 'Magento_Analytics::analytics_api' + ], + ]; + return $integrationData; + } +} diff --git a/app/code/Magento/Analytics/Model/Link.php b/app/code/Magento/Analytics/Model/Link.php new file mode 100644 index 0000000000000..4a40796df4fd0 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Link.php @@ -0,0 +1,54 @@ +url = $url; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/LinkProvider.php b/app/code/Magento/Analytics/Model/LinkProvider.php new file mode 100644 index 0000000000000..2474653f4916c --- /dev/null +++ b/app/code/Magento/Analytics/Model/LinkProvider.php @@ -0,0 +1,87 @@ +linkFactory = $linkFactory; + $this->fileInfoManager = $fileInfoManager; + $this->storeManager = $storeManager; + } + + /** + * Returns base url to file according to store configuration + * + * @param FileInfo $fileInfo + * @return string + */ + private function getBaseUrl(FileInfo $fileInfo) + { + return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $fileInfo->getPath(); + } + + /** + * Verify is requested file ready + * + * @param FileInfo $fileInfo + * @return bool + */ + private function isFileReady(FileInfo $fileInfo) + { + return $fileInfo->getPath() && $fileInfo->getInitializationVector(); + } + + /** + * @inheritdoc + */ + public function get() + { + $fileInfo = $this->fileInfoManager->load(); + if (!$this->isFileReady($fileInfo)) { + throw new NoSuchEntityException(__('File is not ready yet.')); + } + return $this->linkFactory->create( + [ + 'url' => $this->getBaseUrl($fileInfo), + 'initializationVector' => base64_encode($fileInfo->getInitializationVector()) + ] + ); + } +} diff --git a/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php b/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php new file mode 100644 index 0000000000000..174272614fb19 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php @@ -0,0 +1,61 @@ +subscriptionUpdateHandler = $subscriptionUpdateHandler; + } + + /** + * Add additional handling after config value was saved. + * + * @param Value $subject + * @param Value $result + * @return Value + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterSave( + Value $subject, + Value $result + ) { + if ($this->isPluginApplicable($result)) { + $this->subscriptionUpdateHandler->processUrlUpdate($result->getOldValue()); + } + + return $result; + } + + /** + * @param Value $result + * @return bool + */ + private function isPluginApplicable(Value $result) + { + return $result->isValueChanged() + && ($result->getPath() === Store::XML_PATH_SECURE_BASE_URL) + && ($result->getScope() === ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + } +} diff --git a/app/code/Magento/Analytics/Model/ProviderFactory.php b/app/code/Magento/Analytics/Model/ProviderFactory.php new file mode 100644 index 0000000000000..3a23430fca077 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ProviderFactory.php @@ -0,0 +1,39 @@ +objectManager = $objectManager; + } + + /** + * @param string $providerName + * @return object + */ + public function create($providerName) + { + return $this->objectManager->get($providerName); + } +} diff --git a/app/code/Magento/Analytics/Model/ReportUrlProvider.php b/app/code/Magento/Analytics/Model/ReportUrlProvider.php new file mode 100644 index 0000000000000..e7fdf6f9e8132 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportUrlProvider.php @@ -0,0 +1,94 @@ +analyticsToken = $analyticsToken; + $this->otpRequest = $otpRequest; + $this->config = $config; + $this->flagManager = $flagManager; + } + + /** + * Provide URL on resource with reports. + * + * @return string + * @throws SubscriptionUpdateException + */ + public function getUrl() + { + if ($this->flagManager->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE)) { + throw new SubscriptionUpdateException(__( + 'Your Base URL has been changed and your reports are being updated. ' + . 'Advanced Reporting will be available once this change has been processed. Please try again later.' + )); + } + + $url = $this->config->getValue($this->urlReportConfigPath); + if ($this->analyticsToken->isTokenExist()) { + $otp = $this->otpRequest->call(); + if ($otp) { + $query = http_build_query(['otp' => $otp], '', '&'); + $url .= '?' . $query; + } + } + + return $url; + } +} diff --git a/app/code/Magento/Analytics/Model/ReportWriter.php b/app/code/Magento/Analytics/Model/ReportWriter.php new file mode 100644 index 0000000000000..7128658947908 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriter.php @@ -0,0 +1,101 @@ +config = $config; + $this->reportValidator = $reportValidator; + $this->providerFactory = $providerFactory; + } + + /** + * {@inheritdoc} + */ + public function write(WriteInterface $directory, $path) + { + $errorsList = []; + foreach ($this->config->get() as $file) { + $provider = reset($file['providers']); + if (isset($provider['parameters']['name'])) { + $error = $this->reportValidator->validate($provider['parameters']['name']); + if ($error) { + $errorsList[] = $error; + continue; + } + } + /** @var $providerObject */ + $providerObject = $this->providerFactory->create($provider['class']); + $fileName = $provider['parameters'] ? $provider['parameters']['name'] : $provider['name']; + $fileFullPath = $path . $fileName . '.csv'; + $fileData = $providerObject->getReport(...array_values($provider['parameters'])); + $stream = $directory->openFile($fileFullPath, 'w+'); + $stream->lock(); + $headers = []; + foreach ($fileData as $row) { + if (!$headers) { + $headers = array_keys($row); + $stream->writeCsv($headers); + } + $stream->writeCsv($row); + } + $stream->unlock(); + $stream->close(); + } + if ($errorsList) { + $errorStream = $directory->openFile($path . $this->errorsFileName, 'w+'); + foreach ($errorsList as $error) { + $errorStream->lock(); + $errorStream->writeCsv($error); + $errorStream->unlock(); + } + $errorStream->close(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/ReportWriterInterface.php b/app/code/Magento/Analytics/Model/ReportWriterInterface.php new file mode 100644 index 0000000000000..a611095a47ae4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriterInterface.php @@ -0,0 +1,28 @@ +moduleManager = $moduleManager; + } + + /** + * Returns module with module status + * + * @return array + */ + public function current() + { + $current = parent::current(); + if (is_array($current) && isset($current['module_name'])) { + $current['status'] = + $this->moduleManager->isEnabled($current['module_name']) == 1 ? 'Enabled' : "Disabled"; + } + return $current; + } +} diff --git a/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php b/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php new file mode 100644 index 0000000000000..0d226a9de7dc2 --- /dev/null +++ b/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php @@ -0,0 +1,102 @@ +scopeConfig = $scopeConfig; + $this->configPaths = $configPaths; + $this->storeManager = $storeManager; + } + + /** + * Generates report using config paths from di.xml + * For each website and store + * @return \IteratorIterator + */ + public function getReport() + { + $configReport = $this->generateReportForScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0); + + /** @var WebsiteInterface $website */ + foreach ($this->storeManager->getWebsites() as $website) { + $configReport = array_merge( + $this->generateReportForScope(ScopeInterface::SCOPE_WEBSITES, $website->getId()), + $configReport + ); + } + + /** @var StoreInterface $store */ + foreach ($this->storeManager->getStores() as $store) { + $configReport = array_merge( + $this->generateReportForScope(ScopeInterface::SCOPE_STORES, $store->getId()), + $configReport + ); + } + return new \IteratorIterator(new \ArrayIterator($configReport)); + } + + /** + * Creates report from config for scope type and scope id. + * + * @param string $scope + * @param int $scopeId + * @return array + */ + private function generateReportForScope($scope, $scopeId) + { + $report = []; + foreach ($this->configPaths as $configPath) { + $report[] = [ + "config_path" => $configPath, + "scope" => $scope, + "scope_id" => $scopeId, + "value" => $this->scopeConfig->getValue( + $configPath, + $scope, + $scopeId + ) + ]; + } + return $report; + } +} diff --git a/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php new file mode 100644 index 0000000000000..1dd831a672faa --- /dev/null +++ b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php @@ -0,0 +1,120 @@ +scopeConfig = $scopeConfig; + $this->analyticsToken = $analyticsToken; + $this->flagManager = $flagManager; + } + + /** + * Retrieve subscription status to Magento BI Advanced Reporting. + * + * Statuses: + * Enabled - if subscription is enabled and MA token was received; + * Pending - if subscription is enabled and MA token was not received; + * Disabled - if subscription is not enabled. + * Failed - if subscription is enabled and token was not received after attempts ended. + * + * @return string + */ + public function getStatus() + { + $isSubscriptionEnabledInConfig = $this->scopeConfig->getValue('analytics/subscription/enabled'); + if ($isSubscriptionEnabledInConfig) { + return $this->getStatusForEnabledSubscription(); + } + + return $this->getStatusForDisabledSubscription(); + } + + /** + * Retrieve status for subscription that enabled in config. + * + * @return string + */ + public function getStatusForEnabledSubscription() + { + $status = static::ENABLED; + if ($this->flagManager->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE)) { + $status = self::PENDING; + } + + if (!$this->analyticsToken->isTokenExist()) { + $status = static::PENDING; + if ($this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) === null) { + $status = static::FAILED; + } + } + + return $status; + } + + /** + * Retrieve status for subscription that disabled in config. + * + * @return string + */ + public function getStatusForDisabledSubscription() + { + return static::DISABLED; + } +} diff --git a/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php b/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php new file mode 100644 index 0000000000000..9aaa2ebb3b56f --- /dev/null +++ b/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php @@ -0,0 +1,80 @@ +subscriptionStatusProvider = $subscriptionStatusProvider; + $this->urlBuilder = $urlBuilder; + } + + /** + * @inheritdoc + * + * @codeCoverageIgnore + */ + public function getIdentity() + { + return hash('sha256', 'ANALYTICS_NOTIFICATION'); + } + + /** + * {@inheritdoc} + */ + public function isDisplayed() + { + return $this->subscriptionStatusProvider->getStatus() === SubscriptionStatusProvider::FAILED; + } + + /** + * {@inheritdoc} + */ + public function getText() + { + $messageDetails = ''; + + $messageDetails .= __('Failed to synchronize data to the Magento Business Intelligence service. '); + $messageDetails .= __( + 'Retry Synchronization', + $this->urlBuilder->getUrl('analytics/subscription/retry') + ); + + return $messageDetails; + } + + /** + * @inheritdoc + * + * @codeCoverageIgnore + */ + public function getSeverity() + { + return self::SEVERITY_MAJOR; + } +} diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md new file mode 100644 index 0000000000000..7ec64abcd9b86 --- /dev/null +++ b/app/code/Magento/Analytics/README.md @@ -0,0 +1,41 @@ +# Magento_Analytics Module + +The Magento_Analytics module integrates your Magento instance with the [Magento Business Intelligence (MBI)](https://magento.com/products/business-intelligence) to use [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html) functionality. + +The module implements the following functionality: + +* enabling subscription to the MBI and automatic re-subscription +* changing the base URL with the same MBI account remained +* declaring the configuration schemas for report data collection +* collecting the Magento instance data as reports for the MBI +* introducing API that provides the collected data +* extending Magento configuration with the module parameters: + * subscription status (enabled/disabled) + * industry (a business area in which the instance website works) + * time of data collection (time of the day when the module collects data) + +## Structure + +Beyond the [usual module file structure](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) the module contains a directory `ReportXml`. +[Report XML](http://devdocs.magento.com/guides/v2.2/advanced-reporting/report-xml.html) is a markup language used to build reports for Advanced Reporting. +The language declares SQL queries using XML declaration. + +## Subscription Process + +The subscription to the MBI service is enabled during the installation process of the Analytics module. Each administrator will be notified of these new features upon their initial login to the Admin Panel. + +## Analytics Settings + +Configuration settings for the Analytics module can be modified in the Admin Panel on the Stores > Configuration page under the General > Advanced Reporting tab. + +The following options can be adjusted: +* Advanced Reporting Service (Enabled/Disabled) + * Alters the status of the Advanced Reporting subscription +* Time of day to send data (Hour/Minute/Second in the store's time zone) + * Defines when the data collection process for the Advanced Reporting service occurs +* Industry + * Defines the industry of the store in order to create a personalized Advanced Reporting experience + +## Extensibility + +We do not recommend to extend the Magento_Analytics module. It introduces an API that is purposed to transfer the collected data. Note that the API cannot be used for other needs. diff --git a/app/code/Magento/Analytics/ReportXml/Config.php b/app/code/Magento/Analytics/ReportXml/Config.php new file mode 100644 index 0000000000000..f50dcf941bf50 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config.php @@ -0,0 +1,43 @@ +data = $data; + } + + /** + * Returns config value by name + * + * @param string $queryName + * @return array + */ + public function get($queryName) + { + return $this->data->get($queryName); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php b/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php new file mode 100644 index 0000000000000..9e0b20a6ad414 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php @@ -0,0 +1,61 @@ +hasAttributes()) { + $attrs = $source->attributes; + foreach ($attrs as $attr) { + $result[$attr->name] = $attr->value; + } + } + if ($source->hasChildNodes()) { + $children = $source->childNodes; + if ($children->length == 1) { + $child = $children->item(0); + if ($child->nodeType == XML_TEXT_NODE) { + $result['_value'] = $child->nodeValue; + return count($result) == 1 ? $result['_value'] : $result; + } + } + foreach ($children as $child) { + if ($child instanceof \DOMCharacterData) { + continue; + } + $result[$child->nodeName][] = $this->convertNode($child); + } + } + return $result; + } + + /** + * Converts XML document into corresponding array. + * + * @param \DOMDocument $source + * @return array + */ + public function convert($source) + { + return $this->convertNode($source); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/Mapper.php b/app/code/Magento/Analytics/ReportXml/Config/Mapper.php new file mode 100644 index 0000000000000..4dda8f3c733a6 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Mapper.php @@ -0,0 +1,37 @@ +readers = $readers; + $this->mapper = $mapper; + } + + /** + * Reads configuration according to the given scope. + * + * @param string|null $scope + * @return array + */ + public function read($scope = null) + { + $data = []; + foreach ($this->readers as $reader) { + $data = array_merge_recursive($data, $reader->read($scope)); + } + return $this->mapper->execute($data); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/ConfigInterface.php b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php new file mode 100644 index 0000000000000..ec03ddf429c06 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php @@ -0,0 +1,23 @@ +resourceConnection = $resourceConnection; + $this->objectManager = $objectManager; + } + + /** + * Creates one-time connection for export + * + * @param string $connectionName + * @return AdapterInterface + */ + public function getConnection($connectionName) + { + $connection = $this->resourceConnection->getConnection($connectionName); + $connectionClassName = get_class($connection); + $configData = $connection->getConfig(); + $configData['use_buffered_query'] = false; + unset($configData['persistent']); + return $this->objectManager->create( + $connectionClassName, + [ + 'config' => $configData + ] + ); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php new file mode 100644 index 0000000000000..083b4843c185a --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php @@ -0,0 +1,27 @@ +conditionResolver = $conditionResolver; + $this->nameResolver = $nameResolver; + } + + /** + * Assembles WHERE conditions + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + if (!isset($queryConfig['source']['filter'])) { + return $selectBuilder; + } + $filters = $this->conditionResolver->getFilter( + $selectBuilder, + $queryConfig['source']['filter'], + $this->nameResolver->getAlias($queryConfig['source']) + ); + $selectBuilder->setFilters(array_merge_recursive($selectBuilder->getFilters(), [$filters])); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php new file mode 100644 index 0000000000000..811119ace221b --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php @@ -0,0 +1,69 @@ +nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Assembles FROM condition + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + $selectBuilder->setFrom( + [ + $this->nameResolver->getAlias($queryConfig['source']) => + $this->resourceConnection + ->getTableName($this->nameResolver->getName($queryConfig['source'])), + ] + ); + $columns = $this->columnsResolver->getColumns($selectBuilder, $queryConfig['source']); + $selectBuilder->setColumns(array_merge($selectBuilder->getColumns(), $columns)); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php new file mode 100644 index 0000000000000..f3c6540a25171 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php @@ -0,0 +1,113 @@ +conditionResolver = $conditionResolver; + $this->nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Assembles JOIN conditions + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + if (!isset($queryConfig['source']['link-source'])) { + return $selectBuilder; + } + $joins = []; + $filters = $selectBuilder->getFilters(); + + $sourceAlias = $this->nameResolver->getAlias($queryConfig['source']); + + foreach ($queryConfig['source']['link-source'] as $join) { + $joinAlias = $this->nameResolver->getAlias($join); + + $joins[$joinAlias] = [ + 'link-type' => isset($join['link-type']) ? $join['link-type'] : 'left', + 'table' => [ + $joinAlias => $this->resourceConnection + ->getTableName($this->nameResolver->getName($join)), + ], + 'condition' => $this->conditionResolver->getFilter( + $selectBuilder, + $join['using'], + $joinAlias, + $sourceAlias + ) + ]; + if (isset($join['filter'])) { + $filters = array_merge( + $filters, + [ + $this->conditionResolver->getFilter( + $selectBuilder, + $join['filter'], + $joinAlias, + $sourceAlias + ) + ] + ); + } + $columns = $this->columnsResolver->getColumns($selectBuilder, isset($join['attribute']) ? $join : []); + $selectBuilder->setColumns(array_merge($selectBuilder->getColumns(), $columns)); + } + $selectBuilder->setFilters($filters); + $selectBuilder->setJoins(array_merge($selectBuilder->getJoins(), $joins)); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php new file mode 100644 index 0000000000000..e6474d4c5dc6d --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php @@ -0,0 +1,100 @@ +nameResolver = $nameResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Returns connection + * + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (!$this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + return $this->connection; + } + + /** + * Set columns list to SelectBuilder + * + * @param SelectBuilder $selectBuilder + * @param array $entityConfig + * @return array + */ + public function getColumns(SelectBuilder $selectBuilder, $entityConfig) + { + if (!isset($entityConfig['attribute'])) { + return []; + } + $group = []; + $columns = $selectBuilder->getColumns(); + foreach ($entityConfig['attribute'] as $attributeData) { + $columnAlias = $this->nameResolver->getAlias($attributeData); + $tableAlias = $this->nameResolver->getAlias($entityConfig); + $columnName = $this->nameResolver->getName($attributeData); + if (isset($attributeData['function'])) { + $prefix = ''; + if (isset($attributeData['distinct']) && $attributeData['distinct'] == true) { + $prefix = ' DISTINCT '; + } + $expression = new ColumnValueExpression( + strtoupper($attributeData['function']) . '(' . $prefix + . $this->getConnection()->quoteIdentifier($tableAlias . '.' . $columnName) + . ')' + ); + } else { + $expression = $tableAlias . '.' . $columnName; + } + $columns[$columnAlias] = $expression; + if (isset($attributeData['group'])) { + $group[$columnAlias] = $expression; + } + } + $selectBuilder->setGroup(array_merge($selectBuilder->getGroup(), $group)); + return $columns; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php new file mode 100644 index 0000000000000..773b96959e794 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php @@ -0,0 +1,166 @@ + '%1$s = %2$s', + 'neq' => '%1$s != %2$s', + 'like' => '%1$s LIKE %2$s', + 'nlike' => '%1$s NOT LIKE %2$s', + 'in' => '%1$s IN(%2$s)', + 'nin' => '%1$s NOT IN(%2$s)', + 'notnull' => '%1$s IS NOT NULL', + 'null' => '%1$s IS NULL', + 'gt' => '%1$s > %2$s', + 'lt' => '%1$s < %2$s', + 'gteq' => '%1$s >= %2$s', + 'lteq' => '%1$s <= %2$s', + 'finset' => 'FIND_IN_SET(%2$s, %1$s)' + ]; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * ConditionResolver constructor. + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * Returns connection + * + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (!$this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + return $this->connection; + } + + /** + * Returns value for condition + * + * @param string $condition + * @param string $referencedEntity + * @return mixed|null|string|\Zend_Db_Expr + */ + private function getValue($condition, $referencedEntity) + { + $value = null; + $argument = isset($condition['_value']) ? $condition['_value'] : null; + if (!isset($condition['type'])) { + $condition['type'] = 'value'; + } + + switch ($condition['type']) { + case "value": + $value = $this->getConnection()->quote($argument); + break; + case "variable": + $value = new Expression($argument); + break; + case "identifier": + $value = $this->getConnection()->quoteIdentifier( + $referencedEntity ? $referencedEntity . '.' . $argument : $argument + ); + break; + } + return $value; + } + + /** + * Returns condition for WHERE + * + * @param SelectBuilder $selectBuilder + * @param string $tableName + * @param array $condition + * @param null|string $referencedEntity + * @return string + */ + private function getCondition(SelectBuilder $selectBuilder, $tableName, $condition, $referencedEntity = null) + { + $columns = $selectBuilder->getColumns(); + if (isset($columns[$condition['attribute']]) + && $columns[$condition['attribute']] instanceof Expression + ) { + $expression = $columns[$condition['attribute']]; + } else { + $expression = $this->getConnection()->quoteIdentifier($tableName . '.' . $condition['attribute']); + } + return sprintf( + $this->conditionMap[$condition['operator']], + $expression, + $this->getValue($condition, $referencedEntity) + ); + } + + /** + * Build WHERE condition + * + * @param SelectBuilder $selectBuilder + * @param array $filterConfig + * @param string $aliasName + * @param null|string $referencedAlias + * @return array + */ + public function getFilter(SelectBuilder $selectBuilder, $filterConfig, $aliasName, $referencedAlias = null) + { + $filtersParts = []; + foreach ($filterConfig as $filter) { + $glue = $filter['glue']; + $parts = []; + foreach ($filter['condition'] as $condition) { + if (isset($condition['type']) && $condition['type'] == 'variable') { + $selectBuilder->setParams(array_merge($selectBuilder->getParams(), [$condition['_value']])); + } + $parts[] = $this->getCondition( + $selectBuilder, + $aliasName, + $condition, + $referencedAlias + ); + } + if (isset($filter['filter'])) { + $parts[] = '(' . $this->getFilter( + $selectBuilder, + $filter['filter'], + $aliasName, + $referencedAlias + ) . ')'; + } + $filtersParts[] = '(' . implode(' ' . strtoupper($glue) . ' ', $parts) . ')'; + } + return implode(' OR ', $filtersParts); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php b/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php new file mode 100644 index 0000000000000..c9543002eb272 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php @@ -0,0 +1,40 @@ +getName($elementConfig); + if (isset($elementConfig['alias'])) { + $alias = $elementConfig['alias']; + } + return $alias; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php b/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php new file mode 100644 index 0000000000000..21a641f0a71c7 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php @@ -0,0 +1,64 @@ +connectionFactory = $connectionFactory; + $this->queryFactory = $queryFactory; + } + + /** + * Tries to do query for provided report with limit 0 and return error information if it failed + * + * @param string $name + * @param SearchCriteriaInterface $criteria + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function validate($name, SearchCriteriaInterface $criteria = null) + { + $query = $this->queryFactory->create($name); + $connection = $this->connectionFactory->getConnection($query->getConnectionName()); + $query->getSelect()->limit(0); + try { + $connection->query($query->getSelect()); + } catch (\Zend_Db_Statement_Exception $e) { + return [$name, $e->getMessage()]; + } + + return []; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php new file mode 100644 index 0000000000000..17f2392758de8 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php @@ -0,0 +1,305 @@ +resourceConnection = $resourceConnection; + } + + /** + * Get join condition + * + * @return array + */ + public function getJoins() + { + return $this->joins; + } + + /** + * Set joins conditions + * + * @param array $joins + * @return $this + */ + public function setJoins($joins) + { + $this->joins = $joins; + + return $this; + } + + /** + * Get connection name + * + * @return string + */ + public function getConnectionName() + { + return $this->connectionName; + } + + /** + * Set connection name + * + * @param string $connectionName + * @return $this + */ + public function setConnectionName($connectionName) + { + $this->connectionName = $connectionName; + + return $this; + } + + /** + * Get columns + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Set columns + * + * @param array $columns + * @return $this + */ + public function setColumns($columns) + { + $this->columns = $columns; + + return $this; + } + + /** + * Get filters + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters + * + * @param array $filters + * @return $this + */ + public function setFilters($filters) + { + $this->filters = $filters; + + return $this; + } + + /** + * Get from condition + * + * @return array + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set from condition + * + * @param array $from + * @return $this + */ + public function setFrom($from) + { + $this->from = $from; + + return $this; + } + + /** + * Process JOIN conditions + * + * @param Select $select + * @param array $joinConfig + * @return Select + */ + private function processJoin(Select $select, $joinConfig) + { + switch ($joinConfig['link-type']) { + case 'left': + $select->joinLeft($joinConfig['table'], $joinConfig['condition'], []); + break; + case 'inner': + $select->joinInner($joinConfig['table'], $joinConfig['condition'], []); + break; + case 'right': + $select->joinRight($joinConfig['table'], $joinConfig['condition'], []); + break; + } + return $select; + } + + /** + * Creates Select object + * + * @return Select + */ + public function create() + { + $connection = $this->resourceConnection->getConnection($this->getConnectionName()); + $select = $connection->select(); + $select->from($this->getFrom(), []); + $select->columns($this->getColumns()); + foreach ($this->getFilters() as $filter) { + $select->where($filter); + } + foreach ($this->getJoins() as $joinConfig) { + $select = $this->processJoin($select, $joinConfig); + } + if (!empty($this->getGroup())) { + $select->group(implode(', ', $this->getGroup())); + } + return $select; + } + + /** + * Returns group + * + * @return array + */ + public function getGroup() + { + return $this->group; + } + + /** + * Set group + * + * @param array $group + * @return $this + */ + public function setGroup($group) + { + $this->group = $group; + + return $this; + } + + /** + * Get parameters + * + * @return array + */ + public function getParams() + { + return $this->params; + } + + /** + * Set parameters + * + * @param array $params + * @return $this + */ + public function setParams($params) + { + $this->params = $params; + + return $this; + } + + /** + * Get having condition + * + * @return array + */ + public function getHaving() + { + return $this->having; + } + + /** + * Set having condition + * + * @param array $having + * @return $this + */ + public function setHaving($having) + { + $this->having = $having; + + return $this; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php new file mode 100644 index 0000000000000..1d88d4618efc5 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php @@ -0,0 +1,43 @@ +objectManager = $objectManager; + } + + /** + * Create class instance with specified parameters + * + * @param array $data + * @return SelectBuilder + */ + public function create(array $data = []) + { + return $this->objectManager->create(SelectBuilder::class, $data); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/IteratorFactory.php b/app/code/Magento/Analytics/ReportXml/IteratorFactory.php new file mode 100644 index 0000000000000..a196cef8b66dc --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/IteratorFactory.php @@ -0,0 +1,61 @@ +objectManager = $objectManager; + $this->defaultIteratorName = $defaultIteratorName; + } + + /** + * Creates instance of the result iterator with the query result as an input + * Result iterator can be changed through report configuration + * + * < ... + * + * Uses IteratorIterator by default + * + * @param \Traversable $result + * @param string|null $iteratorName + * @return \IteratorIterator + */ + public function create(\Traversable $result, $iteratorName = null) + { + return $this->objectManager->create( + $iteratorName ?: $this->defaultIteratorName, + [ + 'iterator' => $result + ] + ); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Query.php b/app/code/Magento/Analytics/ReportXml/Query.php new file mode 100644 index 0000000000000..46ec2fb494183 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Query.php @@ -0,0 +1,96 @@ +select = $select; + $this->connectionName = $connectionName; + $this->selectHydrator = $selectHydrator; + $this->config = $config; + } + + /** + * @return Select + */ + public function getSelect() + { + return $this->select; + } + + /** + * @return string + */ + public function getConnectionName() + { + return $this->connectionName; + } + + /** + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Specify data which should be serialized to JSON + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + public function jsonSerialize() + { + return [ + 'connectionName' => $this->getConnectionName(), + 'select_parts' => $this->selectHydrator->extract($this->getSelect()), + 'config' => $this->getConfig() + ]; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/QueryFactory.php b/app/code/Magento/Analytics/ReportXml/QueryFactory.php new file mode 100644 index 0000000000000..8ed7e767b28b3 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/QueryFactory.php @@ -0,0 +1,142 @@ +config = $config; + $this->selectBuilderFactory = $selectBuilderFactory; + $this->assemblers = $assemblers; + $this->queryCache = $queryCache; + $this->objectManager = $objectManager; + $this->selectHydrator = $selectHydrator; + } + + /** + * Returns query connection name according to configuration + * + * @param string $queryConfig + * @return string + */ + private function getQueryConnectionName($queryConfig) + { + $connectionName = 'default'; + if (isset($queryConfig['connection'])) { + $connectionName = $queryConfig['connection']; + } + return $connectionName; + } + + /** + * Create query according to configuration settings + * + * @param string $queryName + * @return Query + */ + private function constructQuery($queryName) + { + $queryConfig = $this->config->get($queryName); + $selectBuilder = $this->selectBuilderFactory->create(); + $selectBuilder->setConnectionName($this->getQueryConnectionName($queryConfig)); + foreach ($this->assemblers as $assembler) { + $selectBuilder = $assembler->assemble($selectBuilder, $queryConfig); + } + $select = $selectBuilder->create(); + return $this->objectManager->create( + Query::class, + [ + 'select' => $select, + 'selectHydrator' => $this->selectHydrator, + 'connectionName' => $selectBuilder->getConnectionName(), + 'config' => $queryConfig + ] + ); + } + + /** + * Creates query by name + * + * @param string $queryName + * @return Query + */ + public function create($queryName) + { + $cached = $this->queryCache->load($queryName); + if ($cached) { + $queryData = json_decode($cached, true); + return $this->objectManager->create( + Query::class, + [ + 'select' => $this->selectHydrator->recreate($queryData['select_parts']), + 'selectHydrator' => $this->selectHydrator, + 'connectionName' => $queryData['connectionName'], + 'config' => $queryData['config'] + ] + ); + } + $query = $this->constructQuery($queryName); + $this->queryCache->save(json_encode($query), $queryName); + return $query; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/ReportProvider.php b/app/code/Magento/Analytics/ReportXml/ReportProvider.php new file mode 100644 index 0000000000000..247be5fa8e4ca --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ReportProvider.php @@ -0,0 +1,76 @@ +queryFactory = $queryFactory; + $this->connectionFactory = $connectionFactory; + $this->iteratorFactory = $iteratorFactory; + } + + /** + * Returns custom iterator name for report + * Null for default + * + * @param Query $query + * @return string|null + */ + private function getIteratorName(Query $query) + { + $config = $query->getConfig(); + return $config['iterator'] ?? null; + } + + /** + * Returns report data by name and criteria + * + * @param string $name + * @return \IteratorIterator + */ + public function getReport($name) + { + $query = $this->queryFactory->create($name); + $connection = $this->connectionFactory->getConnection($query->getConnectionName()); + $statement = $connection->query($query->getSelect()); + return $this->iteratorFactory->create($statement, $this->getIteratorName($query)); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/SelectHydrator.php b/app/code/Magento/Analytics/ReportXml/SelectHydrator.php new file mode 100644 index 0000000000000..02cde2fd0598d --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/SelectHydrator.php @@ -0,0 +1,143 @@ +resourceConnection = $resourceConnection; + $this->objectManager = $objectManager; + $this->selectParts = $selectParts; + } + + /** + * @return array + */ + private function getSelectParts() + { + return array_merge($this->predefinedSelectParts, $this->selectParts); + } + + /** + * Extracts Select metadata parts + * + * @param Select $select + * @return array + * @throws \Zend_Db_Select_Exception + */ + public function extract(Select $select) + { + $parts = []; + foreach ($this->getSelectParts() as $partName) { + $parts[$partName] = $select->getPart($partName); + } + return $parts; + } + + /** + * @param array $selectParts + * @return Select + */ + public function recreate(array $selectParts) + { + $select = $this->resourceConnection->getConnection()->select(); + + $select = $this->processColumns($select, $selectParts); + + foreach ($selectParts as $partName => $partValue) { + $select->setPart($partName, $partValue); + } + + return $select; + } + + /** + * Process COLUMNS part values and add this part into select. + * + * If each column contains information about select expression + * an object with the type of this expression going to be created and assigned to this column. + * + * @param Select $select + * @param array $selectParts + * @return Select + */ + private function processColumns(Select $select, array &$selectParts) + { + if (!empty($selectParts[Select::COLUMNS]) && is_array($selectParts[Select::COLUMNS])) { + $part = []; + + foreach ($selectParts[Select::COLUMNS] as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if (is_array($column) && !empty($column['class'])) { + $expression = $this->objectManager->create( + $column['class'], + isset($column['arguments']) ? $column['arguments'] : [] + ); + $part[] = [$correlationName, $expression, $alias]; + } else { + $part[] = $columnEntry; + } + } + + $select->setPart(Select::COLUMNS, $part); + unset($selectParts[Select::COLUMNS]); + } + + return $select; + } +} diff --git a/app/code/Magento/Analytics/Setup/InstallData.php b/app/code/Magento/Analytics/Setup/InstallData.php new file mode 100644 index 0000000000000..aaa619bbb0caa --- /dev/null +++ b/app/code/Magento/Analytics/Setup/InstallData.php @@ -0,0 +1,53 @@ +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/Test/Mftf/LICENSE.txt b/app/code/Magento/Analytics/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Analytics/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/Analytics/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/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/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php new file mode 100644 index 0000000000000..407e323aeaae6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php @@ -0,0 +1,77 @@ +abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment', 'getLabel']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->additionalComment = $objectManager->getObject( + AdditionalComment::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('New comment'); + $this->abstractElementMock->expects($this->any()) + ->method('getLabel') + ->willReturn('Comment label'); + $html = $this->additionalComment->render($this->abstractElementMock); + $this->assertRegExp( + "/New comment/", + $html + ); + $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 new file mode 100644 index 0000000000000..54612076a757f --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php @@ -0,0 +1,81 @@ +abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->setMethods(['getLocaleDate']) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + $this->timeZoneMock = $this->getMockBuilder(TimezoneInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock->expects($this->any()) + ->method('getLocaleDate') + ->willReturn($this->timeZoneMock); + + $objectManager = new ObjectManager($this); + $this->collectionTimeLabel = $objectManager->getObject( + CollectionTimeLabel::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $timeZone = "America/New_York"; + $this->abstractElementMock->setForm($this->formMock); + $this->timeZoneMock->expects($this->once()) + ->method('getConfigTimezone') + ->willReturn($timeZone); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('Eastern Standard Time (America/New_York)'); + $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 new file mode 100644 index 0000000000000..0806187ebac01 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php @@ -0,0 +1,85 @@ +subscriptionStatusProviderMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment']) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->subscriptionStatusLabel = $objectManager->getObject( + SubscriptionStatusLabel::class, + [ + 'context' => $this->contextMock, + 'subscriptionStatusProvider' => $this->subscriptionStatusProviderMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->subscriptionStatusProviderMock->expects($this->once()) + ->method('getStatus') + ->willReturn('Enabled'); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('Subscription status: Enabled'); + $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 new file mode 100644 index 0000000000000..6a0cecc781062 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php @@ -0,0 +1,77 @@ +abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment', 'getLabel', 'getHint']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->vertical = $objectManager->getObject( + Vertical::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('New comment'); + $this->abstractElementMock->expects($this->any()) + ->method('getHint') + ->willReturn('New hint'); + $html = $this->vertical->render($this->abstractElementMock); + $this->assertRegExp( + "/New comment/", + $html + ); + $this->assertRegExp( + "/New hint/", + $html + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php new file mode 100644 index 0000000000000..6f613cdc4d639 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php @@ -0,0 +1,84 @@ +configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultRedirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->signUpController = $this->objectManagerHelper->getObject( + SignUp::class, + [ + 'config' => $this->configMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $urlBIEssentialsConfigPath = 'analytics/url/bi_essentials'; + $this->configMock->expects($this->once()) + ->method('getValue') + ->with($urlBIEssentialsConfigPath) + ->willReturn('value'); + $this->resultRedirectFactoryMock->expects($this->once())->method('create')->willReturn($this->redirectMock); + $this->redirectMock->expects($this->once())->method('setUrl')->with('value')->willReturnSelf(); + $this->assertEquals($this->redirectMock, $this->signUpController->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php new file mode 100644 index 0000000000000..4f54ce5059965 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php @@ -0,0 +1,185 @@ +reportUrlProviderMock = $this->getMockBuilder(ReportUrlProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->showController = $this->objectManagerHelper->getObject( + Show::class, + [ + 'reportUrlProvider' => $this->reportUrlProviderMock, + 'resultFactory' => $this->resultFactoryMock, + 'messageManager' => $this->messageManagerMock, + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $otpUrl = 'http://example.com?otp=15vbjcfdvd15645'; + + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willReturn($otpUrl); + $this->redirectMock + ->expects($this->once()) + ->method('setUrl') + ->with($otpUrl) + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } + + /** + * @dataProvider executeWithExceptionDataProvider + * + * @param \Exception $exception + */ + public function testExecuteWithException(\Exception $exception) + { + + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willThrowException($exception); + if ($exception instanceof LocalizedException) { + $message = $exception->getMessage(); + } else { + $message = __('Sorry, there has been an error processing your request. Please try again later.'); + } + $this->messageManagerMock + ->expects($this->once()) + ->method('addExceptionMessage') + ->with($exception, $message) + ->willReturnSelf(); + $this->redirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } + + /** + * @return array + */ + public function executeWithExceptionDataProvider() + { + return [ + 'ExecuteWithLocalizedException' => [new LocalizedException(__('TestMessage'))], + 'ExecuteWithException' => [new \Exception('TestMessage')], + ]; + } + + /** + * @return void + */ + public function testExecuteWithSubscriptionUpdateException() + { + $exception = new SubscriptionUpdateException(__('TestMessage')); + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addNoticeMessage') + ->with($exception->getMessage()) + ->willReturnSelf(); + $this->redirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php new file mode 100644 index 0000000000000..17c485a8df230 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php @@ -0,0 +1,159 @@ +resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultRedirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subscriptionHandlerMock = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->retryController = $this->objectManagerHelper->getObject( + Retry::class, + [ + 'resultFactory' => $this->resultFactoryMock, + 'subscriptionHandler' => $this->subscriptionHandlerMock, + 'messageManager' => $this->messageManagerMock, + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->resultRedirectMock); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willReturn(true); + $this->assertSame( + $this->resultRedirectMock, + $this->retryController->execute() + ); + } + + /** + * @dataProvider executeExceptionsDataProvider + * + * @param \Exception $exception + * @param Phrase $message + */ + public function testExecuteWithException(\Exception $exception, Phrase $message) + { + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->resultRedirectMock); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addExceptionMessage') + ->with($exception, $message); + + $this->assertSame( + $this->resultRedirectMock, + $this->retryController->execute() + ); + } + + /** + * @return array + */ + public function executeExceptionsDataProvider() + { + return [ + [new LocalizedException(__('TestMessage')), __('TestMessage')], + [ + new \Exception('TestMessage'), + __('Sorry, there has been an error processing your request. Please try again later.') + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php new file mode 100644 index 0000000000000..81c57d79033c8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php @@ -0,0 +1,91 @@ +exportDataHandlerMock = $this->getMockBuilder(ExportDataHandlerInterface::class) + ->getMockForAbstractClass(); + + $this->subscriptionStatusMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->collectData = $this->objectManagerHelper->getObject( + CollectData::class, + [ + 'exportDataHandler' => $this->exportDataHandlerMock, + 'subscriptionStatus' => $this->subscriptionStatusMock, + ] + ); + } + + /** + * @param string $status + * @return void + * @dataProvider executeDataProvider + */ + public function testExecute($status) + { + $this->subscriptionStatusMock + ->expects($this->once()) + ->method('getStatus') + ->with() + ->willReturn($status); + $this->exportDataHandlerMock + ->expects(($status === SubscriptionStatusProvider::ENABLED) ? $this->once() : $this->never()) + ->method('prepareExportData') + ->with(); + + $this->assertTrue($this->collectData->execute()); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + 'Subscription is enabled' => [SubscriptionStatusProvider::ENABLED], + 'Subscription is disabled' => [SubscriptionStatusProvider::DISABLED], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php new file mode 100644 index 0000000000000..959a11f9e1058 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php @@ -0,0 +1,133 @@ +connectorMock = $this->getMockBuilder(Connector::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->signUp = new SignUp( + $this->connectorMock, + $this->configWriterMock, + $this->flagManagerMock, + $this->reinitableConfigMock + ); + } + + public function testExecute() + { + $attemptsCount = 10; + + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($attemptsCount); + + $attemptsCount -= 1; + $this->flagManagerMock->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); + $this->connectorMock->expects($this->once()) + ->method('execute') + ->with('signUp') + ->willReturn(true); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->flagManagerMock->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $this->assertTrue($this->signUp->execute()); + } + + public function testExecuteFlagNotExist() + { + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(null); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->assertFalse($this->signUp->execute()); + } + + public function testExecuteZeroAttempts() + { + $attemptsCount = 0; + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($attemptsCount); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->flagManagerMock->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $this->assertFalse($this->signUp->execute()); + } + + /** + * Add assertions for method deleteAnalyticsCronExpr. + * + * @return void + */ + private function addDeleteAnalyticsCronExprAsserts() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(SubscriptionHandler::CRON_STRING_PATH) + ->willReturn(true); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->willReturnSelf(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php new file mode 100644 index 0000000000000..ede53d8783a7a --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php @@ -0,0 +1,214 @@ +connectorMock = $this->getMockBuilder(Connector::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->update = new Update( + $this->connectorMock, + $this->configWriterMock, + $this->reinitableConfigMock, + $this->flagManagerMock, + $this->analyticsTokenMock + ); + } + + /** + * @return void + */ + public function testExecuteWithoutToken() + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(10); + $this->connectorMock + ->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn(false); + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->addFinalOutputAsserts(); + $this->assertFalse($this->update->execute()); + } + + /** + * @param bool $isExecuted + */ + private function addFinalOutputAsserts(bool $isExecuted = true) + { + $this->flagManagerMock + ->expects($this->exactly(2 * $isExecuted)) + ->method('deleteFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE], + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE] + ); + $this->configWriterMock + ->expects($this->exactly((int)$isExecuted)) + ->method('delete') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); + $this->reinitableConfigMock + ->expects($this->exactly((int)$isExecuted)) + ->method('reinit') + ->with(); + } + + /** + * @param $counterData + * @return void + * @dataProvider executeWithEmptyReverseCounterDataProvider + */ + public function testExecuteWithEmptyReverseCounter($counterData) + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($counterData); + $this->connectorMock + ->expects($this->never()) + ->method('execute') + ->with('update') + ->willReturn(false); + $this->analyticsTokenMock + ->method('isTokenExist') + ->willReturn(true); + $this->addFinalOutputAsserts(); + $this->assertFalse($this->update->execute()); + } + + /** + * Provides empty states of the reverse counter. + * + * @return array + */ + public function executeWithEmptyReverseCounterDataProvider() + { + return [ + [null], + [0] + ]; + } + + /** + * @param int $reverseCount + * @param bool $commandResult + * @param bool $finalConditionsIsExpected + * @param bool $functionResult + * @return void + * @dataProvider executeRegularScenarioDataProvider + */ + public function testExecuteRegularScenario( + int $reverseCount, + bool $commandResult, + bool $finalConditionsIsExpected, + bool $functionResult + ) { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($reverseCount); + $this->connectorMock + ->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn($commandResult); + $this->analyticsTokenMock + ->method('isTokenExist') + ->willReturn(true); + $this->addFinalOutputAsserts($finalConditionsIsExpected); + $this->assertSame($functionResult, $this->update->execute()); + } + + /** + * @return array + */ + public function executeRegularScenarioDataProvider() + { + return [ + 'The last attempt with command execution result False' => [ + 'Reverse count' => 1, + 'Command result' => false, + 'Executed final output conditions' => true, + 'Function result' => false, + ], + 'Not the last attempt with command execution result False' => [ + 'Reverse count' => 10, + 'Command result' => false, + 'Executed final output conditions' => false, + 'Function result' => false, + ], + 'Command execution result True' => [ + 'Reverse count' => 10, + 'Command result' => true, + 'Executed final output conditions' => true, + 'Function result' => true, + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php b/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php new file mode 100644 index 0000000000000..57315543bc32d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php @@ -0,0 +1,129 @@ +reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->tokenModel = $this->objectManagerHelper->getObject( + AnalyticsToken::class, + [ + 'reinitableConfig' => $this->reinitableConfigMock, + 'config' => $this->configMock, + 'configWriter' => $this->configWriterMock, + 'tokenPath' => $this->tokenPath, + ] + ); + } + + /** + * @return void + */ + public function testStoreToken() + { + $value = 'jjjj0000'; + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with($this->tokenPath, $value); + + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->willReturnSelf(); + + $this->assertTrue($this->tokenModel->storeToken($value)); + } + + /** + * @return void + */ + public function testGetToken() + { + $value = 'jjjj0000'; + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->tokenPath) + ->willReturn($value); + + $this->assertSame($value, $this->tokenModel->getToken()); + } + + /** + * @return void + */ + public function testIsTokenExist() + { + $this->assertFalse($this->tokenModel->isTokenExist()); + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->tokenPath) + ->willReturn('0000'); + $this->assertTrue($this->tokenModel->isTokenExist()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php new file mode 100644 index 0000000000000..f5f721c038c57 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php @@ -0,0 +1,178 @@ +reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->subscriptionUpdateHandler = $this->objectManagerHelper->getObject( + SubscriptionUpdateHandler::class, + [ + 'reinitableConfig' => $this->reinitableConfigMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'flagManager' => $this->flagManagerMock, + 'configWriter' => $this->configWriterMock, + ] + ); + } + + /** + * @return void + */ + public function testTokenDoesNotExist() + { + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(false); + $this->flagManagerMock + ->expects($this->never()) + ->method('saveFlag'); + $this->configWriterMock + ->expects($this->never()) + ->method('save'); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate('http://store.com')); + } + + /** + * @return void + */ + public function testTokenAndPreviousBaseUrlExist() + { + $url = 'https://store.com'; + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue], + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, $url] + ); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->with(); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate($url)); + } + + /** + * @return void + */ + public function testTokenExistAndWithoutPreviousBaseUrl() + { + $url = 'https://store.com'; + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(false); + $this->flagManagerMock + ->expects($this->exactly(2)) + ->method('saveFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, $url], + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue] + ); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->with(); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate($url)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php new file mode 100644 index 0000000000000..071b96111ac8b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php @@ -0,0 +1,111 @@ +configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->collectionTime = $this->objectManagerHelper->getObject( + CollectionTime::class, + [ + 'configWriter' => $this->configWriterMock, + '_logger' => $this->loggerMock, + ] + ); + } + + /** + * @return void + */ + public function testAfterSave() + { + $this->collectionTime->setData('value', '05,04,03'); + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(CollectionTime::CRON_SCHEDULE_PATH, join(' ', ['04', '05', '*', '*', '*'])); + + $this->assertInstanceOf( + Value::class, + $this->collectionTime->afterSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testAfterSaveWrongValue() + { + $this->collectionTime->setData('value', '00,01'); + $this->collectionTime->afterSave(); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testAfterSaveWithLocalizedException() + { + $exception = new \Exception('Test message'); + $this->collectionTime->setData('value', '05,04,03'); + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(CollectionTime::CRON_SCHEDULE_PATH, join(' ', ['04', '05', '*', '*', '*'])) + ->willThrowException($exception); + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($exception->getMessage()); + $this->collectionTime->afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php new file mode 100644 index 0000000000000..82aa4dc72dfe0 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php @@ -0,0 +1,152 @@ +flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->tokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->subscriptionHandler = $this->objectManagerHelper->getObject( + SubscriptionHandler::class, + [ + 'flagManager' => $this->flagManagerMock, + 'configWriter' => $this->configWriterMock, + 'attemptsInitValue' => $this->attemptsInitValue, + 'analyticsToken' => $this->tokenMock, + ] + ); + } + + public function testProcessEnabledTokenExist() + { + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->configWriterMock + ->expects($this->never()) + ->method('save'); + $this->flagManagerMock + ->expects($this->never()) + ->method('saveFlag'); + $this->assertTrue( + $this->subscriptionHandler->processEnabled() + ); + } + + public function testProcessEnabledTokenDoesNotExist() + { + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionHandler::CRON_STRING_PATH, "0 * * * *"); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue) + ->willReturn(true); + $this->assertTrue( + $this->subscriptionHandler->processEnabled() + ); + } + + public function testProcessDisabledTokenDoesNotExist() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(CollectionTime::CRON_SCHEDULE_PATH); + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->flagManagerMock + ->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(true); + $this->assertTrue( + $this->subscriptionHandler->processDisabled() + ); + } + + public function testProcessDisabledTokenExists() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(CollectionTime::CRON_SCHEDULE_PATH); + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->flagManagerMock + ->expects($this->never()) + ->method('deleteFlag'); + $this->assertTrue( + $this->subscriptionHandler->processDisabled() + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php new file mode 100644 index 0000000000000..eea3193258bc6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php @@ -0,0 +1,184 @@ +subscriptionHandlerMock = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->enabledModel = $this->objectManagerHelper->getObject( + Enabled::class, + [ + 'subscriptionHandler' => $this->subscriptionHandlerMock, + '_logger' => $this->loggerMock, + 'config' => $this->configMock, + ] + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessEnabled() + { + $this->enabledModel->setData('value', $this->valueEnabled); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(!$this->valueEnabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessDisabled() + { + $this->enabledModel->setData('value', $this->valueDisabled); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(!$this->valueDisabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processDisabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessValueNotChanged() + { + $this->enabledModel->setData('value', null); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(null); + + $this->subscriptionHandlerMock + ->expects($this->never()) + ->method('processEnabled') + ->with() + ->willReturn(true); + $this->subscriptionHandlerMock + ->expects($this->never()) + ->method('processDisabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testExecuteAfterSaveFailedWithLocalizedException() + { + $exception = new \Exception('Message'); + $this->enabledModel->setData('value', $this->valueEnabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willThrowException($exception); + + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($exception->getMessage()); + + $this->enabledModel->afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php new file mode 100644 index 0000000000000..6fe7d0aa93998 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php @@ -0,0 +1,59 @@ +objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Config\Backend\Vertical::class + ); + } + + /** + * @return void + */ + public function testBeforeSaveSuccess() + { + $this->subject->setValue('Apps and Games'); + + $this->assertInstanceOf( + \Magento\Analytics\Model\Config\Backend\Vertical::class, + $this->subject->beforeSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testBeforeSaveFailedWithLocalizedException() + { + $this->subject->setValue(''); + + $this->subject->beforeSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php new file mode 100644 index 0000000000000..0b7f4870dbac8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php @@ -0,0 +1,142 @@ +objectManagerHelper = new ObjectManagerHelper($this); + + $this->mapper = $this->objectManagerHelper->getObject(Mapper::class); + } + + /** + * @param array $configData + * @param array $resultData + * @return void + * + * @dataProvider executingDataProvider + */ + public function testExecution($configData, $resultData) + { + $this->assertSame($resultData, $this->mapper->execute($configData)); + } + + /** + * @return array + */ + public function executingDataProvider() + { + return [ + 'wrongConfig' => [ + ['config' => ['files']], + [] + ], + 'validConfigWithFileNodes' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [[]] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [] + ] + ], + ], + 'validConfigWithProvidersNode' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [ + 0 => [ + 'reportProvider' => [0 => []] + ] + ] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [ + 'reportProvider' => ['parameters' => []] + ] + ] + ], + ], + 'validConfigWithParametersNode' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [ + 0 => [ + 'reportProvider' => [ + 0 => [ + 'parameters' => [ + 0 => ['name' => ['reportName']] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [ + 'reportProvider' => [ + 'parameters' => [ + 'name' => 'reportName' + ] + ] + ] + ] + ], + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php new file mode 100644 index 0000000000000..6aa9c7ef3106c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php @@ -0,0 +1,108 @@ +mapperMock = $this->getMockBuilder(Mapper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->readerXmlMock = $this->getMockBuilder(ReaderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->readerDbMock = $this->getMockBuilder(ReaderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reader = $this->objectManagerHelper->getObject( + Reader::class, + [ + 'mapper' => $this->mapperMock, + 'readers' => [ + $this->readerXmlMock, + $this->readerDbMock, + ], + ] + ); + } + + /** + * @return void + */ + public function testRead() + { + $scope = 'store'; + $xmlReaderResult = [ + 'config' => ['node1' => ['node2' => 'node4']] + ]; + $dbReaderResult = [ + 'config' => ['node1' => ['node2' => 'node3']] + ]; + $mapperResult = ['node2' => ['node3', 'node4']]; + + $this->readerXmlMock + ->expects($this->once()) + ->method('read') + ->with($scope) + ->willReturn($xmlReaderResult); + + $this->readerDbMock + ->expects($this->once()) + ->method('read') + ->with($scope) + ->willReturn($dbReaderResult); + + $this->mapperMock + ->expects($this->once()) + ->method('execute') + ->with(array_merge_recursive($xmlReaderResult, $dbReaderResult)) + ->willReturn($mapperResult); + + $this->assertSame($mapperResult, $this->reader->read($scope)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php new file mode 100644 index 0000000000000..c13205d34f25b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php @@ -0,0 +1,60 @@ +objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Config\Source\Vertical::class, + [ + 'verticals' => [ + 'Apps and Games', + 'Athletic/Sporting Goods', + 'Art and Design' + ] + ] + ); + } + + /** + * @return void + */ + public function testToOptionArray() + { + $expectedOptionsArray = [ + ['value' => '', 'label' => __('--Please Select--')], + ['value' => 'Apps and Games', 'label' => __('Apps and Games')], + ['value' => 'Athletic/Sporting Goods', 'label' => __('Athletic/Sporting Goods')], + ['value' => 'Art and Design', 'label' => __('Art and Design')] + ]; + + $this->assertEquals( + $expectedOptionsArray, + $this->subject->toOptionArray() + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php new file mode 100644 index 0000000000000..8739219ebdf09 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php @@ -0,0 +1,68 @@ +dataInterfaceMock = $this->getMockBuilder(DataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->config = $this->objectManagerHelper->getObject( + Config::class, + [ + 'data' => $this->dataInterfaceMock, + ] + ); + } + + /** + * @return void + */ + public function testGet() + { + $key = 'configKey'; + $defaultValue = 'mock'; + $configValue = 'emptyString'; + + $this->dataInterfaceMock + ->expects($this->once()) + ->method('get') + ->with($key, $defaultValue) + ->willReturn($configValue); + + $this->assertSame($configValue, $this->config->get($key, $defaultValue)); + } +} 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 new file mode 100644 index 0000000000000..f8f3919b2489e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php @@ -0,0 +1,218 @@ +curlMock = $this->getMockBuilder( + \Magento\Framework\HTTP\Adapter\Curl::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder( + \Psr\Log\LoggerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->curlFactoryMock = $this->getMockBuilder(CurlFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->curlFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->curlMock); + + $this->responseFactoryMock = $this->getMockBuilder( + \Magento\Analytics\Model\Connector\Http\ResponseFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->converterMock = $this->createJsonConverter(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Connector\Http\Client\Curl::class, + [ + 'curlFactory' => $this->curlFactoryMock, + 'responseFactory' => $this->responseFactoryMock, + 'converter' => $this->converterMock, + 'logger' => $this->loggerMock, + ] + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + public function getTestData() + { + return [ + [ + 'data' => [ + 'version' => '1.1', + 'body'=> ['name' => 'value'], + 'url' => 'http://www.mystore.com', + 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + ] + ] + ]; + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestSuccess(array $data) + { + $responseString = 'This is response.'; + $response = new \Zend_Http_Response(201, [], $responseString); + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + json_encode($data['body']) + ); + $this->curlMock->expects($this->once()) + ->method('read') + ->willReturn($responseString); + $this->curlMock->expects($this->any()) + ->method('getErrno') + ->willReturn(0); + + $this->responseFactoryMock->expects($this->any()) + ->method('create') + ->with($responseString) + ->willReturn($response); + + $this->assertEquals( + $response, + $this->subject->request( + $data['method'], + $data['url'], + $data['body'], + $data['headers'], + $data['version'] + ) + ); + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestError(array $data) + { + $response = new \Zend_Http_Response(0, []); + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + json_encode($data['body']) + ); + $this->curlMock->expects($this->once()) + ->method('read'); + $this->curlMock->expects($this->atLeastOnce()) + ->method('getErrno') + ->willReturn(1); + $this->curlMock->expects($this->atLeastOnce()) + ->method('getError') + ->willReturn('CURL error.'); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with( + new \Exception( + 'MBI service CURL connection error #1: CURL error.' + ) + ); + + $this->assertEquals( + $response, + $this->subject->request( + $data['method'], + $data['url'], + $data['body'], + $data['headers'], + $data['version'] + ) + ); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createJsonConverter() + { + $converterMock = $this->getMockBuilder(ConverterInterface::class) + ->getMockForAbstractClass(); + $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 new file mode 100644 index 0000000000000..60a19f3d5079e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php @@ -0,0 +1,34 @@ +assertEquals(JsonConverter::CONTENT_TYPE_HEADER, $converter->getContentTypeHeader()); + } + + public function testConvertBody() + { + $body = '{"token": "secret-token"}'; + $converter = new JsonConverter(); + $this->assertEquals(json_decode($body, 1), $converter->fromBody($body)); + } + + public function testConvertData() + { + $data = ["token" => "secret-token"]; + $converter = new JsonConverter(); + $this->assertEquals(json_encode($data), $converter->toBody($data)); + } +} 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 new file mode 100644 index 0000000000000..7c3c484843285 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php @@ -0,0 +1,39 @@ + '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'); + $responseResolver = new ResponseResolver( + new JsonConverter(), + [ + 201 => $responseHandlerMock, + 404 => $notFoundResponseHandlerMock, + ] + ); + $this->assertTrue($responseResolver->getResult($response)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php new file mode 100644 index 0000000000000..cee3877631c2e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php @@ -0,0 +1,107 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $successHandler = $this->getMockBuilder(\Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $successHandler->method('handleResponse') + ->willReturn(true); + + $this->notifyDataChangedCommand = new NotifyDataChangedCommand( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + new ResponseResolver(new JsonConverter(), [201 => $successHandler]), + $this->loggerMock + ); + } + + public function testExecuteSuccess() + { + $configVal = "Config val"; + $token = "Secret token!"; + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($configVal); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($token); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + ZendClient::POST, + $configVal, + ['access-token' => $token, 'url' => $configVal] + )->willReturn(new \Zend_Http_Response(201, [])); + $this->assertTrue($this->notifyDataChangedCommand->execute()); + } + + public function testExecuteWithoutToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->httpClientMock->expects($this->never()) + ->method('request'); + $this->assertFalse($this->notifyDataChangedCommand->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php new file mode 100644 index 0000000000000..8a3f4efb15cf4 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php @@ -0,0 +1,187 @@ +loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subject = new OTPRequest( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + $this->responseResolverMock, + $this->loggerMock + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + 'otp' => 'thisisotp', + 'url' => 'http://www.mystore.com', + 'access-token' => 'thisisaccesstoken', + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + 'body'=> ['access-token' => 'thisisaccesstoken','url' => 'http://www.mystore.com'], + ]; + } + + /** + * @return void + */ + public function testCallSuccess() + { + $data = $this->getTestData(); + + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($data['access-token']); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn(new \Zend_Http_Response(201, [])); + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn($data['otp']); + + $this->assertEquals( + $data['otp'], + $this->subject->call() + ); + } + + /** + * @return void + */ + public function testCallNoAccessToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + + $this->httpClientMock->expects($this->never()) + ->method('request'); + + $this->assertFalse($this->subject->call()); + } + + /** + * @return void + */ + public function testCallNoOtp() + { + $data = $this->getTestData(); + + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($data['access-token']); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn(new \Zend_Http_Response(0, [])); + + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn(false); + + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->assertFalse($this->subject->call()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php new file mode 100644 index 0000000000000..0ff36cca5db2d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php @@ -0,0 +1,22 @@ +assertFalse($OTPHandler->handleResponse([])); + $expectedOtp = 123; + $this->assertEquals($expectedOtp, $OTPHandler->handleResponse(['otp' => $expectedOtp])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php new file mode 100644 index 0000000000000..707003149bcfd --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php @@ -0,0 +1,36 @@ +getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $analyticsToken->expects($this->once()) + ->method('storeToken') + ->with(null); + $subscriptionHandler = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionStatusProvider = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionStatusProvider->method('getStatus')->willReturn(SubscriptionStatusProvider::ENABLED); + $reSignUpHandler = new ReSignUp($analyticsToken, $subscriptionHandler, $subscriptionStatusProvider); + $this->assertFalse($reSignUpHandler->handleResponse([])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php new file mode 100644 index 0000000000000..81711cfc56950 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php @@ -0,0 +1,30 @@ +getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $analyticsToken->expects($this->once()) + ->method('storeToken') + ->with($accessToken); + $signUpHandler = new SignUp($analyticsToken, new JsonConverter()); + $this->assertFalse($signUpHandler->handleResponse([])); + $this->assertEquals($accessToken, $signUpHandler->handleResponse(['access-token' => $accessToken])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php new file mode 100644 index 0000000000000..7779357e8bea7 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php @@ -0,0 +1,20 @@ +assertTrue($updateHandler->handleResponse([])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php new file mode 100644 index 0000000000000..5593496a957b7 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php @@ -0,0 +1,174 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationManagerMock = $this->getMockBuilder(IntegrationManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationToken = $this->getMockBuilder(IntegrationToken::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->signUpCommand = new SignUpCommand( + $this->analyticsTokenMock, + $this->integrationManagerMock, + $this->configMock, + $this->httpClientMock, + $this->loggerMock, + $this->responseResolverMock + ); + } + + public function testExecuteSuccess() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn($this->integrationToken); + $this->integrationManagerMock->expects($this->once()) + ->method('activateIntegration') + ->willReturn(true); + $data = $this->getTestData(); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + $this->integrationToken->expects($this->any()) + ->method('getData') + ->with('token') + ->willReturn($data['integration-token']); + $httpResponse = new \Zend_Http_Response(201, [], '{"access-token": "' . $data['access-token'] . '"}'); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn($httpResponse); + $this->responseResolverMock->expects($this->any()) + ->method('getResult') + ->with($httpResponse) + ->willReturn(true); + $this->assertTrue($this->signUpCommand->execute()); + } + + public function testExecuteFailureCannotGenerateToken() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn(false); + $this->integrationManagerMock->expects($this->never()) + ->method('activateIntegration'); + $this->assertFalse($this->signUpCommand->execute()); + } + + public function testExecuteFailureResponseIsEmpty() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn($this->integrationToken); + $this->integrationManagerMock->expects($this->once()) + ->method('activateIntegration') + ->willReturn(true); + $httpResponse = new \Zend_Http_Response(0, []); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->willReturn($httpResponse); + $this->responseResolverMock->expects($this->any()) + ->method('getResult') + ->willReturn(false); + $this->assertFalse($this->signUpCommand->execute()); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + '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/Model/Connector/UpdateCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php new file mode 100644 index 0000000000000..47253a13530e5 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php @@ -0,0 +1,143 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->updateCommand = new UpdateCommand( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + $this->loggerMock, + $this->flagManagerMock, + $this->responseResolverMock + ); + } + + public function testExecuteSuccess() + { + $url = "old.localhost.com"; + $configVal = "Config val"; + $token = "Secret token!"; + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($configVal); + + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn($url); + + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + ZendClient::PUT, + $configVal, + [ + 'url' => $url, + 'new-url' => $configVal, + 'access-token' => $token + ] + )->willReturn(new \Zend_Http_Response(200, [])); + + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn(true); + + $this->assertTrue($this->updateCommand->execute()); + } + + public function testExecuteWithoutToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + + $this->assertFalse($this->updateCommand->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php new file mode 100644 index 0000000000000..4414b81cbc183 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php @@ -0,0 +1,70 @@ +objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->signUpCommandMock = $this->getMockBuilder(SignUpCommand::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commands = ['signUp' => SignUpCommand::class]; + $this->connector = new Connector($this->commands, $this->objectManagerMock); + } + + public function testExecute() + { + $commandName = 'signUp'; + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with($this->commands[$commandName]) + ->willReturn($this->signUpCommandMock); + $this->signUpCommandMock->expects($this->once()) + ->method('execute') + ->willReturn(true); + $this->assertTrue($this->connector->execute($commandName)); + } + + /** + * @expectedException \Magento\Framework\Exception\NotFoundException + */ + public function testExecuteCommandNotFound() + { + $commandName = 'register'; + $this->connector->execute($commandName); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php new file mode 100644 index 0000000000000..a896c309b4007 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php @@ -0,0 +1,226 @@ +analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextFactoryMock = $this->getMockBuilder(EncodedContextFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->key = ''; + $this->source = ''; + $this->initializationVectors = []; + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->cryptographer = $this->objectManagerHelper->getObject( + Cryptographer::class, + [ + 'analyticsToken' => $this->analyticsTokenMock, + 'encodedContextFactory' => $this->encodedContextFactoryMock, + 'cipherMethod' => $this->cipherMethod, + ] + ); + } + + /** + * @return void + */ + public function testEncode() + { + $token = 'some-token-value'; + $this->source = 'Some text'; + $this->key = hash('sha256', $token); + + $checkEncodedContext = function ($parameters) { + $emptyRequiredParameters = + array_diff(['content', 'initializationVector'], array_keys(array_filter($parameters))); + if ($emptyRequiredParameters) { + return false; + } + + $encryptedData = openssl_encrypt( + $this->source, + $this->cipherMethod, + $this->key, + OPENSSL_RAW_DATA, + $parameters['initializationVector'] + ); + + return ($encryptedData === $parameters['content']); + }; + + $this->analyticsTokenMock + ->expects($this->once()) + ->method('getToken') + ->with() + ->willReturn($token); + + $this->encodedContextFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->callback($checkEncodedContext)) + ->willReturn($this->encodedContextMock); + + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + } + + /** + * @return void + */ + public function testEncodeUniqueInitializationVector() + { + $this->source = 'Some text'; + $token = 'some-token-value'; + + $registerInitializationVector = function ($parameters) { + if (empty($parameters['initializationVector'])) { + return false; + } + + $this->initializationVectors[] = $parameters['initializationVector']; + + return true; + }; + + $this->analyticsTokenMock + ->expects($this->exactly(2)) + ->method('getToken') + ->with() + ->willReturn($token); + + $this->encodedContextFactoryMock + ->expects($this->exactly(2)) + ->method('create') + ->with($this->callback($registerInitializationVector)) + ->willReturn($this->encodedContextMock); + + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + $this->assertCount(2, array_unique($this->initializationVectors)); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @dataProvider encodeNotValidSourceDataProvider + */ + public function testEncodeNotValidSource($source) + { + $this->cryptographer->encode($source); + } + + /** + * @return array + */ + public function encodeNotValidSourceDataProvider() + { + return [ + 'Array' => [[]], + 'Empty string' => [''], + ]; + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testEncodeNotValidCipherMethod() + { + $source = 'Some string'; + $cryptographer = $this->objectManagerHelper->getObject( + Cryptographer::class, + [ + 'cipherMethod' => 'Wrong-method', + ] + ); + + $cryptographer->encode($source); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testEncodeTokenNotValid() + { + $source = 'Some string'; + + $this->analyticsTokenMock + ->expects($this->once()) + ->method('getToken') + ->with() + ->willReturn(null); + + $this->cryptographer->encode($source); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php b/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php new file mode 100644 index 0000000000000..a1a7c54510681 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php @@ -0,0 +1,61 @@ +objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @param string $content + * @param string|null $initializationVector + * @return void + * @dataProvider constructDataProvider + */ + public function testConstruct($content, $initializationVector) + { + $constructorArguments = [ + 'content' => $content, + 'initializationVector' => $initializationVector, + ]; + /** @var EncodedContext $encodedContext */ + $encodedContext = $this->objectManagerHelper->getObject( + EncodedContext::class, + array_filter($constructorArguments) + ); + + $this->assertSame($content, $encodedContext->getContent()); + $this->assertSame($initializationVector ?: '', $encodedContext->getInitializationVector()); + } + + /** + * @return array + */ + public function constructDataProvider() + { + return [ + 'Without Initialization Vector' => ['content text', null], + 'With Initialization Vector' => ['content text', 'c51sd3c4sd68c5sd'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php new file mode 100644 index 0000000000000..1582c241bf45d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php @@ -0,0 +1,74 @@ +objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @return void + */ + public function testThatNotifyExecuted() + { + $expectedResult = true; + $notifyCommandName = 'notifyDataChanged'; + $exportDataHandlerMockObject = $this->createExportDataHandlerMock(); + $analyticsConnectorMockObject = $this->createAnalyticsConnectorMock(); + /** + * @var $exportDataHandlerNotification ExportDataHandlerNotification + */ + $exportDataHandlerNotification = $this->objectManagerHelper->getObject( + ExportDataHandlerNotification::class, + [ + 'exportDataHandler' => $exportDataHandlerMockObject, + 'connector' => $analyticsConnectorMockObject, + ] + ); + $exportDataHandlerMockObject->expects($this->once()) + ->method('prepareExportData') + ->willReturn($expectedResult); + $analyticsConnectorMockObject->expects($this->once()) + ->method('execute') + ->with($notifyCommandName); + $this->assertEquals($expectedResult, $exportDataHandlerNotification->prepareExportData()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createExportDataHandlerMock() + { + return $this->getMockBuilder(ExportDataHandler::class)->disableOriginalConstructor()->getMock(); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createAnalyticsConnectorMock() + { + return $this->getMockBuilder(Connector::class)->disableOriginalConstructor()->getMock(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php new file mode 100644 index 0000000000000..6ffb61d088567 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php @@ -0,0 +1,270 @@ +filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->archiveMock = $this->getMockBuilder(Archive::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->reportWriterMock = $this->getMockBuilder(ReportWriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cryptographerMock = $this->getMockBuilder(Cryptographer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileRecorderMock = $this->getMockBuilder(FileRecorder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(WriteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->exportDataHandler = $this->objectManagerHelper->getObject( + ExportDataHandler::class, + [ + 'filesystem' => $this->filesystemMock, + 'archive' => $this->archiveMock, + 'reportWriter' => $this->reportWriterMock, + 'cryptographer' => $this->cryptographerMock, + 'fileRecorder' => $this->fileRecorderMock, + 'subdirectoryPath' => $this->subdirectoryPath, + 'archiveName' => $this->archiveName, + ] + ); + } + + /** + * @param bool $isArchiveSourceDirectory + * @dataProvider prepareExportDataDataProvider + */ + public function testPrepareExportData($isArchiveSourceDirectory) + { + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $archiveRelativePath = $this->subdirectoryPath . $this->archiveName; + + $archiveSource = $isArchiveSourceDirectory ? (__DIR__) : '/tmp/' . $tmpFilesDirectoryPath; + $archiveAbsolutePath = '/tmp/' . $archiveRelativePath; + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::SYS_TMP) + ->willReturn($this->directoryMock); + $this->directoryMock + ->expects($this->exactly(4)) + ->method('delete') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$archiveRelativePath] + ); + + $this->directoryMock + ->expects($this->exactly(4)) + ->method('getAbsolutePath') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$tmpFilesDirectoryPath], + [$archiveRelativePath], + [$archiveRelativePath] + ) + ->willReturnOnConsecutiveCalls( + $archiveSource, + $archiveSource, + $archiveAbsolutePath, + $archiveAbsolutePath + ); + + $this->reportWriterMock + ->expects($this->once()) + ->method('write') + ->with($this->directoryMock, $tmpFilesDirectoryPath); + + $this->directoryMock + ->expects($this->exactly(2)) + ->method('isExist') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$archiveRelativePath] + ) + ->willReturnOnConsecutiveCalls( + true, + true + ); + + $this->directoryMock + ->expects($this->once()) + ->method('create') + ->with(dirname($archiveRelativePath)); + + $this->archiveMock + ->expects($this->once()) + ->method('pack') + ->with( + $archiveSource, + $archiveAbsolutePath, + $isArchiveSourceDirectory ? true : false + ); + + $fileContent = 'Some text'; + $this->directoryMock + ->expects($this->once()) + ->method('readFile') + ->with($archiveRelativePath) + ->willReturn($fileContent); + + $this->cryptographerMock + ->expects($this->once()) + ->method('encode') + ->with($fileContent) + ->willReturn($this->encodedContextMock); + + $this->fileRecorderMock + ->expects($this->once()) + ->method('recordNewFile') + ->with($this->encodedContextMock); + + $this->assertTrue($this->exportDataHandler->prepareExportData()); + } + + /** + * @return array + */ + public function prepareExportDataDataProvider() + { + return [ + 'Data source for archive is directory' => [true], + 'Data source for archive doesn\'t directory' => [false], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testPrepareExportDataWithLocalizedException() + { + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $archivePath = $this->subdirectoryPath . $this->archiveName; + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::SYS_TMP) + ->willReturn($this->directoryMock); + $this->reportWriterMock + ->expects($this->once()) + ->method('write') + ->with($this->directoryMock, $tmpFilesDirectoryPath); + $this->directoryMock + ->expects($this->exactly(3)) + ->method('delete') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$tmpFilesDirectoryPath], + [$archivePath] + ); + $this->directoryMock + ->expects($this->exactly(2)) + ->method('getAbsolutePath') + ->with($tmpFilesDirectoryPath); + $this->directoryMock + ->expects($this->once()) + ->method('isExist') + ->with($tmpFilesDirectoryPath) + ->willReturn(false); + + $this->assertNull($this->exportDataHandler->prepareExportData()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php new file mode 100644 index 0000000000000..da5f6af3ca4e1 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php @@ -0,0 +1,194 @@ +flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoFactoryMock = $this->getMockBuilder(FileInfoFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->fileInfoManager = $this->objectManagerHelper->getObject( + FileInfoManager::class, + [ + 'flagManager' => $this->flagManagerMock, + 'fileInfoFactory' => $this->fileInfoFactoryMock, + 'flagCode' => $this->flagCode, + 'encodedParameters' => $this->encodedParameters, + ] + ); + } + + /** + * @return void + */ + public function testSave() + { + $path = 'path/to/file'; + $initializationVector = openssl_random_pseudo_bytes(16); + $parameters = [ + 'path' => $path, + 'initializationVector' => $initializationVector, + ]; + + $this->fileInfoMock + ->expects($this->once()) + ->method('getPath') + ->with() + ->willReturn($path); + $this->fileInfoMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn($initializationVector); + + foreach ($this->encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = base64_encode($parameters[$encodedParameter]); + } + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with($this->flagCode, $parameters); + + $this->assertTrue($this->fileInfoManager->save($this->fileInfoMock)); + } + + /** + * @param string|null $path + * @param string|null $initializationVector + * @dataProvider saveWithLocalizedExceptionDataProvider + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testSaveWithLocalizedException($path, $initializationVector) + { + $this->fileInfoMock + ->expects($this->once()) + ->method('getPath') + ->with() + ->willReturn($path); + $this->fileInfoMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn($initializationVector); + + $this->fileInfoManager->save($this->fileInfoMock); + } + + /** + * @return array + */ + public function saveWithLocalizedExceptionDataProvider() + { + return [ + 'Empty FileInfo' => [null, null], + 'FileInfo without IV' => ['path/to/file', null], + ]; + } + + /** + * @dataProvider loadDataProvider + * @param array|null $parameters + */ + public function testLoad($parameters) + { + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with($this->flagCode) + ->willReturn($parameters); + + $processedParameters = $parameters ?: []; + $encodedParameters = array_intersect($this->encodedParameters, array_keys($processedParameters)); + foreach ($encodedParameters as $encodedParameter) { + $processedParameters[$encodedParameter] = base64_decode($processedParameters[$encodedParameter]); + } + + $this->fileInfoFactoryMock + ->expects($this->once()) + ->method('create') + ->with($processedParameters) + ->willReturn($this->fileInfoMock); + + $this->assertSame($this->fileInfoMock, $this->fileInfoManager->load()); + } + + /** + * @return array + */ + public function loadDataProvider() + { + return [ + 'Empty flag data' => [null], + 'Correct flag data' => [[ + 'path' => 'path/to/file', + 'initializationVector' => 'xUJjl54MVke+FvMFSBpRSA==', + ]], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php new file mode 100644 index 0000000000000..43ce833f1f03f --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php @@ -0,0 +1,62 @@ +objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @param string|null $path + * @param string|null $initializationVector + * @return void + * @dataProvider constructDataProvider + */ + public function testConstruct($path, $initializationVector) + { + $constructorArguments = [ + 'path' => $path, + 'initializationVector' => $initializationVector, + ]; + /** @var FileInfo $fileInfo */ + $fileInfo = $this->objectManagerHelper->getObject( + FileInfo::class, + array_filter($constructorArguments) + ); + + $this->assertSame($path ?: '', $fileInfo->getPath()); + $this->assertSame($initializationVector ?: '', $fileInfo->getInitializationVector()); + } + + /** + * @return array + */ + public function constructDataProvider() + { + return [ + 'Degenerate object' => [null, null], + 'Without Initialization Vector' => ['content text', null], + 'With Initialization Vector' => ['content text', 'c51sd3c4sd68c5sd'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php new file mode 100644 index 0000000000000..3c9520bdd995b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php @@ -0,0 +1,209 @@ +fileInfoManagerMock = $this->getMockBuilder(FileInfoManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoFactoryMock = $this->getMockBuilder(FileInfoFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(WriteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->fileRecorder = $this->objectManagerHelper->getObject( + FileRecorder::class, + [ + 'fileInfoManager' => $this->fileInfoManagerMock, + 'fileInfoFactory' => $this->fileInfoFactoryMock, + 'filesystem' => $this->filesystemMock, + 'fileSubdirectoryPath' => $this->fileSubdirectoryPath, + 'encodedFileName' => $this->encodedFileName, + ] + ); + } + + /** + * @param string $pathToExistingFile + * @dataProvider recordNewFileDataProvider + */ + public function testRecordNewFile($pathToExistingFile) + { + $content = openssl_random_pseudo_bytes(200); + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($this->directoryMock); + + $this->encodedContextMock + ->expects($this->once()) + ->method('getContent') + ->with() + ->willReturn($content); + + $hashLength = 64; + $fileRelativePathPattern = '#' . preg_quote($this->fileSubdirectoryPath, '#') + . '.{' . $hashLength . '}/' . preg_quote($this->encodedFileName, '#') . '#'; + $this->directoryMock + ->expects($this->once()) + ->method('writeFile') + ->with($this->matchesRegularExpression($fileRelativePathPattern), $content) + ->willReturn($this->directoryMock); + + $this->fileInfoManagerMock + ->expects($this->once()) + ->method('load') + ->with() + ->willReturn($this->fileInfoMock); + + $this->encodedContextMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn('init_vector***'); + + /** register file */ + $this->fileInfoFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->callback( + function ($parameters) { + return !empty($parameters['path']) && ('init_vector***' === $parameters['initializationVector']); + } + )) + ->willReturn($this->fileInfoMock); + $this->fileInfoManagerMock + ->expects($this->once()) + ->method('save') + ->with($this->fileInfoMock); + + /** remove old file */ + $this->fileInfoMock + ->expects($this->exactly($pathToExistingFile ? 3 : 1)) + ->method('getPath') + ->with() + ->willReturn($pathToExistingFile); + $directoryName = dirname($pathToExistingFile); + if ($directoryName === '.') { + $this->directoryMock + ->expects($this->once()) + ->method('delete') + ->with($pathToExistingFile); + } elseif ($directoryName) { + $this->directoryMock + ->expects($this->exactly(2)) + ->method('delete') + ->withConsecutive( + [$pathToExistingFile], + [$directoryName] + ); + } + + $this->assertTrue($this->fileRecorder->recordNewFile($this->encodedContextMock)); + } + + /** + * @return array + */ + public function recordNewFileDataProvider() + { + return [ + 'File doesn\'t exist' => [''], + 'Existing file into subdirectory' => ['dir_name/file.txt'], + 'Existing file doesn\'t into subdirectory' => ['file.txt'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php new file mode 100644 index 0000000000000..3076a22c85be4 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php @@ -0,0 +1,228 @@ +integrationServiceMock = $this->getMockBuilder(IntegrationServiceInterface::class) + ->getMock(); + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->oauthServiceMock = $this->getMockBuilder(OauthServiceInterface::class) + ->getMock(); + $this->integrationMock = $this->getMockBuilder(Integration::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getId', + 'getConsumerId' + ]) + ->getMock(); + $this->integrationManager = $objectManagerHelper->getObject( + IntegrationManager::class, + [ + 'integrationService' => $this->integrationServiceMock, + 'oauthService' => $this->oauthServiceMock, + 'config' => $this->configMock + ] + ); + } + + /** + * @param string $status + * + * @return array + */ + private function getIntegrationUserData($status) + { + return [ + 'name' => 'ma-integration-user', + 'status' => $status, + 'all_resources' => false, + 'resource' => [ + 'Magento_Analytics::analytics', + 'Magento_Analytics::analytics_api' + ], + ]; + } + + /** + * @return void + */ + public function testActivateIntegrationSuccess() + { + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->exactly(2)) + ->method('getId') + ->willReturn(100500); + $integrationData = $this->getIntegrationUserData(Integration::STATUS_ACTIVE); + $integrationData['integration_id'] = 100500; + $this->configMock->expects($this->exactly(2)) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('update') + ->with($integrationData); + $this->assertTrue($this->integrationManager->activateIntegration()); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testActivateIntegrationFailureNoSuchEntity() + { + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn(null); + $this->configMock->expects($this->once()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->never()) + ->method('update'); + $this->integrationManager->activateIntegration(); + } + + /** + * @dataProvider integrationIdDataProvider + * + * @param int|null $integrationId If null integration is absent. + * @return void + */ + public function testGetTokenNewIntegration($integrationId) + { + $this->configMock->expects($this->atLeastOnce()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getConsumerId') + ->willReturn(100500); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn($integrationId); + if (!$integrationId) { + $this->integrationServiceMock + ->expects($this->once()) + ->method('create') + ->with($this->getIntegrationUserData(Integration::STATUS_INACTIVE)) + ->willReturn($this->integrationMock); + } + $this->oauthServiceMock->expects($this->at(0)) + ->method('getAccessToken') + ->with(100500) + ->willReturn(false); + $this->oauthServiceMock->expects($this->at(2)) + ->method('getAccessToken') + ->with(100500) + ->willReturn('IntegrationToken'); + $this->oauthServiceMock->expects($this->once()) + ->method('createAccessToken') + ->with(100500, true) + ->willReturn(true); + $this->assertEquals('IntegrationToken', $this->integrationManager->generateToken()); + } + + /** + * @dataProvider integrationIdDataProvider + * + * @param int|null $integrationId If null integration is absent. + * @return void + */ + public function testGetTokenExistingIntegration($integrationId) + { + $this->configMock->expects($this->atLeastOnce()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getConsumerId') + ->willReturn(100500); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn($integrationId); + if (!$integrationId) { + $this->integrationServiceMock + ->expects($this->once()) + ->method('create') + ->with($this->getIntegrationUserData(Integration::STATUS_INACTIVE)) + ->willReturn($this->integrationMock); + } + $this->oauthServiceMock->expects($this->once()) + ->method('getAccessToken') + ->with(100500) + ->willReturn('IntegrationToken'); + $this->oauthServiceMock->expects($this->never()) + ->method('createAccessToken'); + $this->assertEquals('IntegrationToken', $this->integrationManager->generateToken()); + } + + /** + * @return array + */ + public function integrationIdDataProvider() + { + return [ + [1], + [null], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php new file mode 100644 index 0000000000000..c7aa2219d1eee --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php @@ -0,0 +1,166 @@ +linkInterfaceFactoryMock = $this->getMockBuilder(LinkInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->fileInfoManagerMock = $this->getMockBuilder(FileInfoManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerInterfaceMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $this->linkInterfaceMock = $this->getMockBuilder(LinkInterface::class) + ->getMockForAbstractClass(); + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeMock = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->linkProvider = $this->objectManagerHelper->getObject( + LinkProvider::class, + [ + 'linkFactory' => $this->linkInterfaceFactoryMock, + 'fileInfoManager' => $this->fileInfoManagerMock, + 'storeManager' => $this->storeManagerInterfaceMock + ] + ); + } + + public function testGet() + { + $baseUrl = 'http://magento.local/pub/media/'; + $fileInfoPath = 'analytics/data.tgz'; + $fileInitializationVector = 'er312esq23eqq'; + $this->fileInfoManagerMock->expects($this->once()) + ->method('load') + ->willReturn($this->fileInfoMock); + $this->linkInterfaceFactoryMock->expects($this->once()) + ->method('create') + ->with( + [ + 'initializationVector' => base64_encode($fileInitializationVector), + 'url' => $baseUrl . $fileInfoPath + ] + ) + ->willReturn($this->linkInterfaceMock); + $this->storeManagerInterfaceMock->expects($this->once()) + ->method('getStore')->willReturn($this->storeMock); + $this->storeMock->expects($this->once()) + ->method('getBaseUrl') + ->with( + UrlInterface::URL_TYPE_MEDIA + ) + ->willReturn($baseUrl); + $this->fileInfoMock->expects($this->atLeastOnce()) + ->method('getPath') + ->willReturn($fileInfoPath); + $this->fileInfoMock->expects($this->atLeastOnce()) + ->method('getInitializationVector') + ->willReturn($fileInitializationVector); + $this->assertEquals($this->linkInterfaceMock, $this->linkProvider->get()); + } + + /** + * @param string|null $fileInfoPath + * @param string|null $fileInitializationVector + * + * @dataProvider fileNotReadyDataProvider + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + * @expectedExceptionMessage File is not ready yet. + */ + public function testFileNotReady($fileInfoPath, $fileInitializationVector) + { + $this->fileInfoManagerMock->expects($this->once()) + ->method('load') + ->willReturn($this->fileInfoMock); + $this->fileInfoMock->expects($this->once()) + ->method('getPath') + ->willReturn($fileInfoPath); + $this->fileInfoMock->expects($this->any()) + ->method('getInitializationVector') + ->willReturn($fileInitializationVector); + $this->linkProvider->get(); + } + + /** + * @return array + */ + public function fileNotReadyDataProvider() + { + return [ + [null, 'initVector'], + ['path', null], + ['', 'initVector'], + ['path', ''], + ['', ''], + [null, null] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php new file mode 100644 index 0000000000000..a89e06562383b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php @@ -0,0 +1,147 @@ +subscriptionUpdateHandlerMock = $this->getMockBuilder(SubscriptionUpdateHandler::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configValueMock = $this->getMockBuilder(Value::class) + ->disableOriginalConstructor() + ->setMethods(['isValueChanged', 'getPath', 'getScope', 'getOldValue']) + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->plugin = $this->objectManagerHelper->getObject( + BaseUrlConfigPlugin::class, + [ + 'subscriptionUpdateHandler' => $this->subscriptionUpdateHandlerMock, + ] + ); + } + + /** + * @param array $configValueData + * @return void + * @dataProvider afterSavePluginIsNotApplicableDataProvider + */ + public function testAfterSavePluginIsNotApplicable( + array $configValueData + ) { + $this->configValueMock + ->method('isValueChanged') + ->willReturn($configValueData['isValueChanged']); + $this->configValueMock + ->method('getPath') + ->willReturn($configValueData['path']); + $this->configValueMock + ->method('getScope') + ->willReturn($configValueData['scope']); + $this->subscriptionUpdateHandlerMock + ->expects($this->never()) + ->method('processUrlUpdate'); + + $this->assertEquals( + $this->configValueMock, + $this->plugin->afterAfterSave($this->configValueMock, $this->configValueMock) + ); + } + + /** + * @return array + */ + public function afterSavePluginIsNotApplicableDataProvider() + { + return [ + 'Value has not been changed' => [ + 'Config Value Data' => [ + 'isValueChanged' => false, + 'path' => Store::XML_PATH_SECURE_BASE_URL, + 'scope' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ], + ], + 'Unsecure URL has been changed' => [ + 'Config Value Data' => [ + 'isValueChanged' => true, + 'path' => Store::XML_PATH_UNSECURE_BASE_URL, + 'scope' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ], + ], + 'Secure URL has been changed not in the Default scope' => [ + 'Config Value Data' => [ + 'isValueChanged' => true, + 'path' => Store::XML_PATH_SECURE_BASE_URL, + 'scope' => ScopeInterface::SCOPE_STORES + ], + ], + ]; + } + + /** + * @return void + */ + public function testAfterSavePluginIsApplicable() + { + $this->configValueMock + ->method('isValueChanged') + ->willReturn(true); + $this->configValueMock + ->method('getPath') + ->willReturn(Store::XML_PATH_SECURE_BASE_URL); + $this->configValueMock + ->method('getScope') + ->willReturn(ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + $this->configValueMock + ->method('getOldValue') + ->willReturn('http://store.com'); + $this->subscriptionUpdateHandlerMock + ->expects($this->once()) + ->method('processUrlUpdate') + ->with('http://store.com'); + + $this->assertEquals( + $this->configValueMock, + $this->plugin->afterAfterSave($this->configValueMock, $this->configValueMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php new file mode 100644 index 0000000000000..0607a977e5b68 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php @@ -0,0 +1,149 @@ +configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->otpRequestMock = $this->getMockBuilder(OTPRequest::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportUrlProvider = $this->objectManagerHelper->getObject( + ReportUrlProvider::class, + [ + 'config' => $this->configMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'otpRequest' => $this->otpRequestMock, + 'flagManager' => $this->flagManagerMock, + 'urlReportConfigPath' => $this->urlReportConfigPath, + ] + ); + } + + /** + * @param bool $isTokenExist + * @param string|null $otp If null OTP was not received. + * + * @dataProvider getUrlDataProvider + */ + public function testGetUrl($isTokenExist, $otp) + { + $reportUrl = 'https://example.com/report'; + $url = ''; + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->urlReportConfigPath) + ->willReturn($reportUrl); + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn($isTokenExist); + $this->otpRequestMock + ->expects($isTokenExist ? $this->once() : $this->never()) + ->method('call') + ->with() + ->willReturn($otp); + if ($isTokenExist && $otp) { + $url = $reportUrl . '?' . http_build_query(['otp' => $otp], '', '&'); + } + $this->assertSame($url ?: $reportUrl, $this->reportUrlProvider->getUrl()); + } + + /** + * @return array + */ + public function getUrlDataProvider() + { + return [ + 'TokenDoesNotExist' => [false, null], + 'TokenExistAndOtpEmpty' => [true, null], + 'TokenExistAndOtpValid' => [true, '249e6b658877bde2a77bc4ab'], + ]; + } + + /** + * @return void + */ + public function testGetUrlWhenSubscriptionUpdateRunning() + { + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn('http://store.com'); + $this->expectException(SubscriptionUpdateException::class); + $this->reportUrlProvider->getUrl(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php new file mode 100644 index 0000000000000..d9b030b84d639 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php @@ -0,0 +1,213 @@ +configInterfaceMock = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); + $this->reportValidatorMock = $this->getMockBuilder(ReportValidator::class) + ->disableOriginalConstructor()->getMock(); + $this->providerFactoryMock = $this->getMockBuilder(ProviderFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->reportProviderMock = $this->getMockBuilder(ReportProvider::class) + ->disableOriginalConstructor()->getMock(); + $this->directoryMock = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportWriter = $this->objectManagerHelper->getObject( + ReportWriter::class, + [ + 'config' => $this->configInterfaceMock, + 'reportValidator' => $this->reportValidatorMock, + 'providerFactory' => $this->providerFactoryMock + ] + ); + } + + /** + * @param array $configData + * @return void + * + * @dataProvider configDataProvider + */ + public function testWrite(array $configData) + { + $errors = []; + $fileData = [ + ['number' => 1, 'type' => 'Shoes Usual'] + ]; + $this->configInterfaceMock + ->expects($this->once()) + ->method('get') + ->with() + ->willReturn([$configData]); + $this->providerFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->providerClass) + ->willReturn($this->reportProviderMock); + $parameterName = isset(reset($configData)[0]['parameters']['name']) + ? reset($configData)[0]['parameters']['name'] + : ''; + $this->reportProviderMock->expects($this->once()) + ->method('getReport') + ->with($parameterName ?: null) + ->willReturn($fileData); + $errorStreamMock = $this->getMockBuilder( + \Magento\Framework\Filesystem\File\WriteInterface::class + )->getMockForAbstractClass(); + $errorStreamMock + ->expects($this->once()) + ->method('lock') + ->with(); + $errorStreamMock + ->expects($this->exactly(2)) + ->method('writeCsv') + ->withConsecutive( + [array_keys($fileData[0])], + [$fileData[0]] + ); + $errorStreamMock->expects($this->once())->method('unlock'); + $errorStreamMock->expects($this->once())->method('close'); + if ($parameterName) { + $this->reportValidatorMock + ->expects($this->once()) + ->method('validate') + ->with($parameterName) + ->willReturn($errors); + } + $this->directoryMock + ->expects($this->once()) + ->method('openFile') + ->with( + $this->stringContains('/var/tmp' . $parameterName ?: $this->reportName), + 'w+' + )->willReturn($errorStreamMock); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @param array $configData + * @return void + * + * @dataProvider configDataProvider + */ + public function testWriteErrorFile($configData) + { + $errors = ['orders', 'SQL Error: test']; + $this->configInterfaceMock->expects($this->once())->method('get')->willReturn([$configData]); + $errorStreamMock = $this->getMockBuilder( + \Magento\Framework\Filesystem\File\WriteInterface::class + )->getMockForAbstractClass(); + $errorStreamMock->expects($this->once())->method('lock'); + $errorStreamMock->expects($this->once())->method('writeCsv')->with($errors); + $errorStreamMock->expects($this->once())->method('unlock'); + $errorStreamMock->expects($this->once())->method('close'); + $this->reportValidatorMock->expects($this->once())->method('validate')->willReturn($errors); + $this->directoryMock->expects($this->once())->method('openFile')->with('/var/tmp' . 'errors.csv', 'w+') + ->willReturn($errorStreamMock); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @return void + */ + public function testWriteEmptyReports() + { + $this->configInterfaceMock->expects($this->once())->method('get')->willReturn([]); + $this->reportValidatorMock->expects($this->never())->method('validate'); + $this->directoryMock->expects($this->never())->method('openFile'); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @return array + */ + public function configDataProvider() + { + return [ + 'reportProvider' => [ + [ + 'providers' => [ + [ + 'name' => $this->providerName, + 'class' => $this->providerClass, + 'parameters' => [ + 'name' => $this->reportName + ], + ] + ] + ] + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php new file mode 100644 index 0000000000000..f314d77f32b41 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php @@ -0,0 +1,50 @@ +moduleManagerMock = $this->getMockBuilder(ModuleManager::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->moduleIterator = $objectManagerHelper->getObject( + ModuleIterator::class, + [ + 'moduleManager' => $this->moduleManagerMock, + 'iterator' => new \ArrayIterator([0 => ['module_name' => 'Coco_Module']]) + ] + ); + } + + public function testCurrent() + { + $this->moduleManagerMock->expects($this->once()) + ->method('isEnabled') + ->with('Coco_Module') + ->willReturn(true); + foreach ($this->moduleIterator as $item) { + $this->assertEquals(['module_name' => 'Coco_Module', 'status' => 'Enabled'], $item); + } + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php new file mode 100644 index 0000000000000..cc46d175543ad --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php @@ -0,0 +1,123 @@ +scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->websiteMock = $this->getMockBuilder(WebsiteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configPaths = [ + 'web/unsecure/base_url', + 'currency/options/base', + 'general/locale/timezone' + ]; + + $this->storeConfigurationProvider = new StoreConfigurationProvider( + $this->scopeConfigMock, + $this->storeManagerMock, + $this->configPaths + ); + } + + public function testGetReport() + { + $map = [ + ['web/unsecure/base_url', 'default', 0, '127.0.0.1'], + ['currency/options/base', 'default', 0, 'USD'], + ['general/locale/timezone', 'default', 0, 'America/Dawson'], + ['web/unsecure/base_url', 'websites', 1, '127.0.0.2'], + ['currency/options/base', 'websites', 1, 'USD'], + ['general/locale/timezone', 'websites', 1, 'America/Belem'], + ['web/unsecure/base_url', 'stores', 2, '127.0.0.3'], + ['currency/options/base', 'stores', 2, 'USD'], + ['general/locale/timezone', 'stores', 2, 'America/Phoenix'], + ]; + + $this->scopeConfigMock + ->method('getValue') + ->will($this->returnValueMap($map)); + + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([$this->websiteMock]); + + $this->storeManagerMock->expects($this->once()) + ->method('getStores') + ->willReturn([$this->storeMock]); + + $this->websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->storeMock->expects($this->once()) + ->method('getId') + ->willReturn(2); + $result = iterator_to_array($this->storeConfigurationProvider->getReport()); + $resultValues = []; + foreach ($result as $item) { + $resultValues[] = array_values($item); + } + array_multisort($resultValues); + array_multisort($map); + $this->assertEquals($resultValues, $map); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php new file mode 100644 index 0000000000000..d6b041ce03178 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php @@ -0,0 +1,196 @@ +scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->getMockForAbstractClass(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->statusProvider = $this->objectManagerHelper->getObject( + SubscriptionStatusProvider::class, + [ + 'scopeConfig' => $this->scopeConfigMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'flagManager' => $this->flagManagerMock, + ] + ); + } + + /** + * @param array $flagManagerData + * @dataProvider getStatusShouldBeFailedDataProvider + */ + public function testGetStatusShouldBeFailed(array $flagManagerData) + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + + $this->expectFlagManagerReturn($flagManagerData); + $this->assertEquals(SubscriptionStatusProvider::FAILED, $this->statusProvider->getStatus()); + } + + /** + * @return array + */ + public function getStatusShouldBeFailedDataProvider() + { + return [ + 'Subscription update doesn\'t active' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, null], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + ], + 'Subscription update is active' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + ], + ]; + } + + /** + * @param array $flagManagerData + * @param bool $isTokenExist + * @dataProvider getStatusShouldBePendingDataProvider + */ + public function testGetStatusShouldBePending(array $flagManagerData, bool $isTokenExist) + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn($isTokenExist); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + + $this->expectFlagManagerReturn($flagManagerData); + $this->assertEquals(SubscriptionStatusProvider::PENDING, $this->statusProvider->getStatus()); + } + + /** + * @return array + */ + public function getStatusShouldBePendingDataProvider() + { + return [ + 'Subscription update doesn\'t active and the token does not exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, null], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, 45] + ], + 'isTokenExist' => false, + ], + 'Subscription update is active and the token does not exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, 45] + ], + 'isTokenExist' => false, + ], + 'Subscription update is active and token exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + 'isTokenExist' => true, + ], + ]; + } + + public function testGetStatusShouldBeEnabled() + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(null); + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + $this->assertEquals(SubscriptionStatusProvider::ENABLED, $this->statusProvider->getStatus()); + } + + public function testGetStatusShouldBeDisabled() + { + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(false); + $this->assertEquals(SubscriptionStatusProvider::DISABLED, $this->statusProvider->getStatus()); + } + + /** + * @param array $mapping + */ + private function expectFlagManagerReturn(array $mapping) + { + $this->flagManagerMock + ->method('getFlagData') + ->willReturnMap($mapping); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php b/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php new file mode 100644 index 0000000000000..ad1d87488d751 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php @@ -0,0 +1,106 @@ +subscriptionStatusMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlBuilderMock = $this->getMockBuilder(UrlInterface::class) + ->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->notification = $this->objectManagerHelper->getObject( + NotificationAboutFailedSubscription::class, + [ + 'subscriptionStatusProvider' => $this->subscriptionStatusMock, + 'urlBuilder' => $this->urlBuilderMock + ] + ); + } + + public function testIsDisplayedWhenMessageShouldBeDisplayed() + { + $this->subscriptionStatusMock->expects($this->once()) + ->method('getStatus') + ->willReturn( + SubscriptionStatusProvider::FAILED + ); + $this->assertTrue($this->notification->isDisplayed()); + } + + /** + * @dataProvider notDisplayedNotificationStatuses + * + * @param $status + */ + public function testIsDisplayedWhenMessageShouldNotBeDisplayed($status) + { + $this->subscriptionStatusMock->expects($this->once()) + ->method('getStatus') + ->willReturn($status); + $this->assertFalse($this->notification->isDisplayed()); + } + + public function testGetTextShouldBuildMessage() + { + $retryUrl = 'http://magento.dev/retryUrl'; + $this->urlBuilderMock->expects($this->once()) + ->method('getUrl') + ->with('analytics/subscription/retry') + ->willReturn($retryUrl); + $messageDetails = 'Failed to synchronize data to the Magento Business Intelligence service. '; + $messageDetails .= sprintf('Retry Synchronization', $retryUrl); + $this->assertEquals($messageDetails, $this->notification->getText()); + } + + /** + * Provide statuses according to which message should not be displayed. + * + * @return array + */ + public function notDisplayedNotificationStatuses() + { + return [ + [SubscriptionStatusProvider::PENDING], + [SubscriptionStatusProvider::DISABLED], + [SubscriptionStatusProvider::ENABLED], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php new file mode 100644 index 0000000000000..3f1ed9a5cf4c0 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php @@ -0,0 +1,121 @@ +objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\Config\Converter\Xml::class + ); + } + + /** + * @return void + */ + public function testConvertNoElements() + { + $this->assertEmpty( + $this->subject->convert(new \DOMDocument()) + ); + } + + /** + * @return void + */ + public function testConvert() + { + $dom = new \DOMDocument(); + + $expectedArray = [ + 'config' => [ + [ + 'noNamespaceSchemaLocation' => 'urn:magento:module:Magento_Analytics:etc/reports.xsd', + 'report' => [ + [ + 'name' => 'test_report_1', + 'connection' => 'sales', + 'source' => [ + [ + 'name' => 'sales_order', + 'alias' => 'orders', + 'attribute' => [ + [ + 'name' => 'entity_id', + 'alias' => 'identifier', + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'gt', + '_value' => '10' + ] + ] + ] + ] + ] + ] + ], + [ + 'name' => 'test_report_2', + 'connection' => 'default', + 'source' => [ + [ + 'name' => 'customer_entity', + 'alias' => 'customers', + 'attribute' => [ + [ + 'name' => 'email' + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'dob', + 'operator' => 'null' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + + $dom->loadXML(file_get_contents(__DIR__ . '/../_files/valid_reports.xml')); + + $this->assertEquals($expectedArray, $this->subject->convert($dom)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php new file mode 100644 index 0000000000000..85343b6b301d6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php @@ -0,0 +1,47 @@ +mapper = new Mapper(); + } + + public function testExecute() + { + $configData['config'][0]['report'] = [ + [ + 'source' => ['product'], + 'name' => 'Product', + ] + ]; + $expectedResult = [ + 'Product' => [ + 'source' => 'product', + 'name' => 'Product', + ] + ]; + $this->assertEquals($this->mapper->execute($configData), $expectedResult); + } + + public function testExecuteWithoutReports() + { + $configData = []; + $this->assertEquals($this->mapper->execute($configData), []); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml new file mode 100644 index 0000000000000..e04ee96163797 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml @@ -0,0 +1,25 @@ + + + + + + + + 10 + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php new file mode 100644 index 0000000000000..cbc9aa129d874 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php @@ -0,0 +1,64 @@ +dataMock = $this->getMockBuilder(DataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->config = $this->objectManagerHelper->getObject( + Config::class, + [ + 'data' => $this->dataMock, + ] + ); + } + + public function testGet() + { + $queryName = 'query string'; + $queryResult = [ 'query' => 1 ]; + + $this->dataMock + ->expects($this->once()) + ->method('get') + ->with($queryName) + ->willReturn($queryResult); + + $this->assertSame($queryResult, $this->config->get($queryName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php new file mode 100644 index 0000000000000..1e4ae9142c13d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php @@ -0,0 +1,106 @@ +resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(MysqlPdoAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionNewMock = $this->getMockBuilder(MysqlPdoAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->connectionFactory = $this->objectManagerHelper->getObject( + ConnectionFactory::class, + [ + 'resourceConnection' => $this->resourceConnectionMock, + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + public function testGetConnection() + { + $connectionName = 'read'; + + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + + $this->connectionMock + ->expects($this->once()) + ->method('getConfig') + ->with() + ->willReturn(['persistent' => 1]); + + $this->objectManagerMock + ->expects($this->once()) + ->method('create') + ->with(get_class($this->connectionMock), ['config' => ['use_buffered_query' => false]]) + ->willReturn($this->connectionNewMock); + + $this->assertSame($this->connectionNewMock, $this->connectionFactory->getConnection($connectionName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php new file mode 100644 index 0000000000000..3b01105a8873b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php @@ -0,0 +1,143 @@ +nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getFilters') + ->willReturn([]); + + $this->conditionResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ConditionResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler::class, + [ + 'conditionResolver' => $this->conditionResolverMock, + 'nameResolver' => $this->nameResolverMock + ] + ); + } + + /** + * @return void + */ + public function testAssembleEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales' + ] + ]; + + $this->selectBuilderMock->expects($this->never()) + ->method('setFilters'); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @return void + */ + public function testAssembleNotEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'null' + ] + ] + ] + ] + ] + ]; + + $this->nameResolverMock->expects($this->any()) + ->method('getAlias') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['alias']); + + $this->conditionResolverMock->expects($this->once()) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['filter'], + $queryConfigMock['source']['alias'] + ) + ->willReturn('(sales.entity_id IS NULL)'); + + $this->selectBuilderMock->expects($this->once()) + ->method('setFilters') + ->with(['(sales.entity_id IS NULL)']); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php new file mode 100644 index 0000000000000..575db94a7b7e1 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php @@ -0,0 +1,167 @@ +nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn([]); + + $this->columnsResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ColumnsResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\FromAssembler::class, + [ + 'nameResolver' => $this->nameResolverMock, + 'columnsResolver' => $this->columnsResolverMock, + 'resourceConnection' => $this->resourceConnection, + ] + ); + } + + /** + * @dataProvider assembleDataProvider + * @param array $queryConfig + * @param string $tableName + * @return void + */ + public function testAssemble(array $queryConfig, $tableName) + { + $this->nameResolverMock->expects($this->any()) + ->method('getAlias') + ->with($queryConfig['source']) + ->willReturn($queryConfig['source']['alias']); + + $this->nameResolverMock->expects($this->once()) + ->method('getName') + ->with($queryConfig['source']) + ->willReturn($queryConfig['source']['name']); + + $this->resourceConnection + ->expects($this->once()) + ->method('getTableName') + ->with($queryConfig['source']['name']) + ->willReturn($tableName); + + $this->selectBuilderMock->expects($this->once()) + ->method('setFrom') + ->with([$queryConfig['source']['alias'] => $tableName]); + + $this->columnsResolverMock->expects($this->once()) + ->method('getColumns') + ->with($this->selectBuilderMock, $queryConfig['source']) + ->willReturn(['entity_id' => 'sales.entity_id']); + + $this->selectBuilderMock->expects($this->once()) + ->method('setColumns') + ->with(['entity_id' => 'sales.entity_id']); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfig) + ); + } + + /** + * @return array + */ + public function assembleDataProvider() + { + return [ + 'Tables without prefixes' => [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'attribute' => [ + [ + 'name' => 'entity_id' + ] + ], + ], + ], + 'sales_order', + ], + 'Tables with prefixes' => [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'attribute' => [ + [ + 'name' => 'entity_id' + ] + ], + ], + ], + 'pref_sales_order', + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php new file mode 100644 index 0000000000000..aaafd731552a0 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php @@ -0,0 +1,279 @@ +nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getFilters') + ->willReturn([]); + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn([]); + $this->selectBuilderMock->expects($this->any()) + ->method('getJoins') + ->willReturn([]); + + $this->columnsResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ColumnsResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->conditionResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ConditionResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler::class, + [ + 'conditionResolver' => $this->conditionResolverMock, + 'nameResolver' => $this->nameResolverMock, + 'columnsResolver' => $this->columnsResolverMock, + 'resourceConnection' => $this->resourceConnection, + ] + ); + } + + /** + * @return void + */ + public function testAssembleEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales' + ] + ]; + + $this->selectBuilderMock->expects($this->never()) + ->method('setColumns'); + $this->selectBuilderMock->expects($this->never()) + ->method('setFilters'); + $this->selectBuilderMock->expects($this->never()) + ->method('setJoins'); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @param array $queryConfigMock + * @param array $joinsMock + * @param array $tablesMapping + * @return void + * @dataProvider assembleNotEmptyDataProvider + */ + public function testAssembleNotEmpty(array $queryConfigMock, array $joinsMock, array $tablesMapping) + { + $filtersMock = []; + + $this->nameResolverMock->expects($this->at(0)) + ->method('getAlias') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['alias']); + $this->nameResolverMock->expects($this->at(1)) + ->method('getAlias') + ->with($queryConfigMock['source']['link-source'][0]) + ->willReturn($queryConfigMock['source']['link-source'][0]['alias']); + $this->nameResolverMock->expects($this->once()) + ->method('getName') + ->with($queryConfigMock['source']['link-source'][0]) + ->willReturn($queryConfigMock['source']['link-source'][0]['name']); + + $this->resourceConnection + ->expects($this->any()) + ->method('getTableName') + ->willReturnOnConsecutiveCalls(...array_values($tablesMapping)); + + $this->conditionResolverMock->expects($this->at(0)) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['link-source'][0]['using'], + $queryConfigMock['source']['link-source'][0]['alias'], + $queryConfigMock['source']['alias'] + ) + ->willReturn('(billing.parent_id = `sales`.`entity_id`)'); + + if (isset($queryConfigMock['source']['link-source'][0]['filter'])) { + $filtersMock = ['(sales.entity_id IS NULL)']; + + $this->conditionResolverMock->expects($this->at(1)) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['link-source'][0]['filter'], + $queryConfigMock['source']['link-source'][0]['alias'], + $queryConfigMock['source']['alias'] + ) + ->willReturn($filtersMock[0]); + + $this->columnsResolverMock->expects($this->once()) + ->method('getColumns') + ->with($this->selectBuilderMock, $queryConfigMock['source']['link-source'][0]) + ->willReturn( + [ + 'entity_id' => 'sales.entity_id', + 'billing_address_id' => 'billing.entity_id' + ] + ); + + $this->selectBuilderMock->expects($this->once()) + ->method('setColumns') + ->with( + [ + 'entity_id' => 'sales.entity_id', + 'billing_address_id' => 'billing.entity_id' + ] + ); + } + + $this->selectBuilderMock->expects($this->once()) + ->method('setFilters') + ->with($filtersMock); + $this->selectBuilderMock->expects($this->once()) + ->method('setJoins') + ->with($joinsMock); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @return array + */ + public function assembleNotEmptyDataProvider() + { + return [ + [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'link-source' => [ + [ + 'name' => 'sales_order_address', + 'alias' => 'billing', + 'link-type' => 'left', + 'attribute' => [ + [ + 'alias' => 'billing_address_id', + 'name' => 'entity_id' + ] + ], + 'using' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'parent_id', + 'operator' => 'eq', + 'type' => 'identifier', + '_value' => 'entity_id' + ] + ] + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'null' + ] + ] + ] + ] + ] + ] + ] + ], + [ + 'billing' => [ + 'link-type' => 'left', + 'table' => [ + 'billing' => 'pref_sales_order_address' + ], + 'condition' => '(billing.parent_id = `sales`.`entity_id`)' + ] + ], + ['sales_order_address' => 'pref_sales_order_address'] + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php new file mode 100644 index 0000000000000..bdbe3d1d22c22 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php @@ -0,0 +1,150 @@ +selectBuilderMock = $this->getMockBuilder(SelectBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManagerHelper($this); + $this->columnsResolver = $objectManager->getObject( + ColumnsResolver::class, + [ + 'nameResolver' => new NameResolver(), + 'resourceConnection' => $this->resourceConnectionMock + ] + ); + } + + public function testGetColumnsWithoutAttributes() + { + $this->assertEquals($this->columnsResolver->getColumns($this->selectBuilderMock, []), []); + } + + /** + * @dataProvider getColumnsDataProvider + */ + public function testGetColumnsWithFunction($expectedColumns, $expectedGroup, $entityConfig) + { + $this->resourceConnectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->any()) + ->method('quoteIdentifier') + ->with('cpe.name') + ->willReturn('`cpe`.`name`'); + $this->selectBuilderMock->expects($this->once()) + ->method('getColumns') + ->willReturn([]); + $this->selectBuilderMock->expects($this->once()) + ->method('getGroup') + ->willReturn([]); + $this->selectBuilderMock->expects($this->once()) + ->method('setGroup') + ->with($expectedGroup); + $this->assertEquals( + $expectedColumns, + $this->columnsResolver->getColumns( + $this->selectBuilderMock, + $entityConfig + ) + ); + } + + /** + * @return array + */ + public function getColumnsDataProvider() + { + return [ + 'COUNT( DISTINCT `cpe`.`name`) AS name' => [ + 'expectedColumns' => [ + 'name' => new ColumnValueExpression('COUNT( DISTINCT `cpe`.`name`)') + ], + 'expectedGroup' => [ + 'name' => new ColumnValueExpression('COUNT( DISTINCT `cpe`.`name`)') + ], + 'entityConfig' => + [ + 'name' => 'catalog_product_entity', + 'alias' => 'cpe', + 'attribute' => [ + [ + 'name' => 'name', + 'function' => 'COUNT', + 'distinct' => true, + 'group' => true + ] + ], + ], + ], + 'AVG(`cpe`.`name`) AS avg_name' => [ + 'expectedColumns' => [ + 'avg_name' => new ColumnValueExpression('AVG(`cpe`.`name`)') + ], + 'expectedGroup' => [], + 'entityConfig' => + [ + 'name' => 'catalog_product_entity', + 'alias' => 'cpe', + 'attribute' => [ + [ + 'name' => 'name', + 'alias' => 'avg_name', + 'function' => 'AVG', + ] + ], + ], + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php new file mode 100644 index 0000000000000..c8182d068fba5 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php @@ -0,0 +1,108 @@ +resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder(SelectBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->conditionResolver = new ConditionResolver($this->resourceConnectionMock); + } + + public function testGetFilter() + { + $condition = ["type" => "variable", "_value" => "1", "attribute" => "id", "operator" => "neq"]; + $valueCondition = ["type" => "value", "_value" => "2", "attribute" => "first_name", "operator" => "eq"]; + $identifierCondition = [ + "type" => "identifier", + "_value" => "other_field", + "attribute" => "last_name", + "operator" => "eq"]; + $filter = [["glue" => "AND", "condition" => [$valueCondition]]]; + $filterConfig = [ + ["glue" => "OR", "condition" => [$condition], 'filter' => $filter], + ["glue" => "OR", "condition" => [$identifierCondition]], + ]; + $aliasName = 'n'; + $this->selectBuilderMock->expects($this->any()) + ->method('setParams') + ->with(array_merge([], [$condition['_value']])); + + $this->selectBuilderMock->expects($this->once()) + ->method('getParams') + ->willReturn([]); + + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn(['price' => new Expression("(n.price = 400)")]); + + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); + + $this->connectionMock->expects($this->any()) + ->method('quote') + ->willReturn("'John'"); + $this->connectionMock->expects($this->exactly(4)) + ->method('quoteIdentifier') + ->willReturnMap([ + ['n.id', false, '`n`.`id`'], + ['n.first_name', false, '`n`.`first_name`'], + ['n.last_name', false, '`n`.`last_name`'], + ['other_field', false, '`other_field`'], + ]); + + $result = "(`n`.`id` != 1 OR ((`n`.`first_name` = 'John'))) OR (`n`.`last_name` = `other_field`)"; + $this->assertEquals( + $result, + $this->conditionResolver->getFilter($this->selectBuilderMock, $filterConfig, $aliasName) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php new file mode 100644 index 0000000000000..4accd03aef3ea --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php @@ -0,0 +1,90 @@ +nameResolverMock = $this->getMockBuilder(NameResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getName']) + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->nameResolver = $this->objectManagerHelper->getObject(NameResolver::class); + } + + public function testGetName() + { + $elementConfigMock = [ + 'name' => 'sales_order', + 'alias' => 'sales', + ]; + + $this->assertSame('sales_order', $this->nameResolver->getName($elementConfigMock)); + } + + /** + * @param array $elementConfig + * @param string|null $elementAlias + * + * @dataProvider getAliasDataProvider + */ + public function testGetAlias($elementConfig, $elementAlias) + { + $elementName = 'elementName'; + + $this->nameResolverMock + ->expects($this->once()) + ->method('getName') + ->with($elementConfig) + ->willReturn($elementName); + + $this->assertSame($elementAlias ?: $elementName, $this->nameResolverMock->getAlias($elementConfig)); + } + + /** + * @return array + */ + public function getAliasDataProvider() + { + return [ + 'ElementConfigWithAliases' => [ + ['alias' => 'sales', 'name' => 'sales_order'], + 'sales', + ], + 'ElementConfigWithoutAliases' => [ + ['name' => 'sales_order'], + null, + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php new file mode 100644 index 0000000000000..bbb9ca4b511b6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php @@ -0,0 +1,125 @@ +connectionFactoryMock = $this->getMockBuilder(ConnectionFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->queryFactoryMock = $this->getMockBuilder(QueryFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->queryMock = $this->getMockBuilder(Query::class)->disableOriginalConstructor() + ->getMock(); + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass(); + $this->selectMock = $this->getMockBuilder(Select::class)->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportValidator = $this->objectManagerHelper->getObject( + ReportValidator::class, + [ + 'connectionFactory' => $this->connectionFactoryMock, + 'queryFactory' => $this->queryFactoryMock + ] + ); + } + + /** + * @dataProvider errorDataProvider + * @param string $reportName + * @param array $result + * @param \PHPUnit_Framework_MockObject_Stub $queryReturnStub + */ + public function testValidate($reportName, $result, \PHPUnit_Framework_MockObject_Stub $queryReturnStub) + { + $connectionName = 'testConnection'; + $this->queryFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->queryMock); + $this->queryMock->expects($this->once())->method('getConnectionName')->willReturn($connectionName); + $this->connectionFactoryMock->expects($this->once())->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + $this->queryMock->expects($this->atLeastOnce())->method('getSelect')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('limit')->with(0); + $this->connectionMock->expects($this->once())->method('query')->with($this->selectMock)->will($queryReturnStub); + $this->assertEquals($result, $this->reportValidator->validate($reportName)); + } + + /** + * Provide variations of the error returning + * + * @return array + */ + public function errorDataProvider() + { + $reportName = 'test'; + $errorMessage = 'SQL Error 42'; + return [ + [ + $reportName, + 'expectedResult' => [], + 'queryReturnStub' => $this->returnValue(null) + ], + [ + $reportName, + 'expectedResult' => [$reportName, $errorMessage], + 'queryReturnStub' => $this->throwException(new \Zend_Db_Statement_Exception($errorMessage)) + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php new file mode 100644 index 0000000000000..1c60822aca795 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php @@ -0,0 +1,103 @@ +resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilder = new SelectBuilder($this->resourceConnectionMock); + } + + public function testCreate() + { + $connectionName = 'MySql'; + $from = ['customer c']; + $columns = ['id', 'name', 'price']; + $filter = 'filter'; + $joins = [ + ['link-type' => 'left', 'table' => 'customer', 'condition' => 'in'], + ['link-type' => 'inner', 'table' => 'price', 'condition' => 'eq'], + ['link-type' => 'right', 'table' => 'attribute', 'condition' => 'neq'], + ]; + $groups = ['id', 'name']; + $this->selectBuilder->setConnectionName($connectionName) + ->setFrom($from) + ->setColumns($columns) + ->setFilters([$filter]) + ->setJoins($joins) + ->setGroup($groups); + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->selectMock); + $this->selectMock->expects($this->once()) + ->method('from') + ->with($from, []); + $this->selectMock->expects($this->once()) + ->method('columns') + ->with($columns); + $this->selectMock->expects($this->once()) + ->method('where') + ->with($filter); + $this->selectMock->expects($this->once()) + ->method('joinLeft') + ->with($joins[0]['table'], $joins[0]['condition'], []); + $this->selectMock->expects($this->once()) + ->method('joinInner') + ->with($joins[1]['table'], $joins[1]['condition'], []); + $this->selectMock->expects($this->once()) + ->method('joinRight') + ->with($joins[2]['table'], $joins[2]['condition'], []); + $this->selectBuilder->create(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php new file mode 100644 index 0000000000000..1d3f293ed676a --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php @@ -0,0 +1,59 @@ +objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorIteratorMock = $this->getMockBuilder(\IteratorIterator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorFactory = new IteratorFactory( + $this->objectManagerMock + ); + } + + public function testCreate() + { + $arrayObject = new \ArrayIterator([1, 2, 3, 4, 5]); + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(\IteratorIterator::class, ['iterator' => $arrayObject]) + ->willReturn($this->iteratorIteratorMock); + + $this->assertEquals($this->iteratorFactory->create($arrayObject), $this->iteratorIteratorMock); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php new file mode 100644 index 0000000000000..9a3805a50f167 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php @@ -0,0 +1,239 @@ +queryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Query::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Config::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder( + \Magento\Framework\DB\Select::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assemblerMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\Assembler\AssemblerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryCacheMock = $this->getMockBuilder( + \Magento\Framework\App\CacheInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder( + \Magento\Framework\ObjectManagerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectHydratorMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\SelectHydrator::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilderFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\QueryFactory::class, + [ + 'config' => $this->configMock, + 'selectBuilderFactory' => $this->selectBuilderFactoryMock, + 'assemblers' => [$this->assemblerMock], + 'queryCache' => $this->queryCacheMock, + 'objectManager' => $this->objectManagerMock, + 'selectHydrator' => $this->selectHydratorMock + ] + ); + } + + /** + * @return void + */ + public function testCreateCached() + { + $queryName = 'test_query'; + + $this->queryCacheMock->expects($this->any()) + ->method('load') + ->with($queryName) + ->willReturn('{"connectionName":"sales","config":{},"select_parts":{}}'); + + $this->selectHydratorMock->expects($this->any()) + ->method('recreate') + ->with([]) + ->willReturn($this->selectMock); + + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with( + \Magento\Analytics\ReportXml\Query::class, + [ + 'select' => $this->selectMock, + 'selectHydrator' => $this->selectHydratorMock, + 'connectionName' => 'sales', + 'config' => [] + ] + ) + ->willReturn($this->queryMock); + + $this->queryCacheMock->expects($this->never()) + ->method('save'); + + $this->assertEquals( + $this->queryMock, + $this->subject->create($queryName) + ); + } + + /** + * @return void + */ + public function testCreateNotCached() + { + $queryName = 'test_query'; + + $queryConfigMock = [ + 'name' => 'test_query', + 'connection' => 'sales' + ]; + + $selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $selectBuilderMock->expects($this->once()) + ->method('setConnectionName') + ->with($queryConfigMock['connection']); + $selectBuilderMock->expects($this->any()) + ->method('create') + ->willReturn($this->selectMock); + $selectBuilderMock->expects($this->any()) + ->method('getConnectionName') + ->willReturn($queryConfigMock['connection']); + + $this->queryCacheMock->expects($this->any()) + ->method('load') + ->with($queryName) + ->willReturn(null); + + $this->configMock->expects($this->any()) + ->method('get') + ->with($queryName) + ->willReturn($queryConfigMock); + + $this->selectBuilderFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($selectBuilderMock); + + $this->assemblerMock->expects($this->once()) + ->method('assemble') + ->with($selectBuilderMock, $queryConfigMock) + ->willReturn($selectBuilderMock); + + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with( + \Magento\Analytics\ReportXml\Query::class, + [ + 'select' => $this->selectMock, + 'selectHydrator' => $this->selectHydratorMock, + 'connectionName' => $queryConfigMock['connection'], + 'config' => $queryConfigMock + ] + ) + ->willReturn($this->queryMock); + + $this->queryCacheMock->expects($this->once()) + ->method('save') + ->with(json_encode($this->queryMock), $queryName); + + $this->assertEquals( + $this->queryMock, + $this->subject->create($queryName) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php new file mode 100644 index 0000000000000..fd924abdd9f44 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php @@ -0,0 +1,90 @@ +selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectHydratorMock = $this->getMockBuilder(SelectHydrator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->query = $this->objectManagerHelper->getObject( + Query::class, + [ + 'select' => $this->selectMock, + 'connectionName' => $this->connectionName, + 'selectHydrator' => $this->selectHydratorMock, + 'config' => [] + ] + ); + } + + /** + * @return void + */ + public function testJsonSerialize() + { + $selectParts = ['part' => 1]; + + $this->selectHydratorMock + ->expects($this->once()) + ->method('extract') + ->with($this->selectMock) + ->willReturn($selectParts); + + $expectedResult = [ + 'connectionName' => $this->connectionName, + 'select_parts' => $selectParts, + 'config' => [] + ]; + + $this->assertSame($expectedResult, $this->query->jsonSerialize()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php new file mode 100644 index 0000000000000..5f329993dd291 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php @@ -0,0 +1,180 @@ +selectMock = $this->getMockBuilder( + \Magento\Framework\DB\Select::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Query::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->queryMock->expects($this->any()) + ->method('getSelect') + ->willReturn($this->selectMock); + + $this->iteratorMock = $this->getMockBuilder( + \IteratorIterator::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->statementMock = $this->getMockBuilder( + \Magento\Framework\DB\Statement\Pdo\Mysql::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->statementMock->expects($this->any()) + ->method('getIterator') + ->willReturn($this->iteratorMock); + + $this->connectionMock = $this->getMockBuilder( + \Magento\Framework\DB\Adapter\AdapterInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\QueryFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\IteratorFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->iteratorMock = $this->getMockBuilder( + \IteratorIterator::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->connectionFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\ConnectionFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\ReportProvider::class, + [ + 'queryFactory' => $this->queryFactoryMock, + 'connectionFactory' => $this->connectionFactoryMock, + 'iteratorFactory' => $this->iteratorFactoryMock + ] + ); + } + + /** + * @return void + */ + public function testGetReport() + { + $reportName = 'test_report'; + $connectionName = 'sales'; + + $this->queryFactoryMock->expects($this->once()) + ->method('create') + ->with($reportName) + ->willReturn($this->queryMock); + + $this->connectionFactoryMock->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + + $this->queryMock->expects($this->once()) + ->method('getConnectionName') + ->willReturn($connectionName); + + $this->queryMock->expects($this->once()) + ->method('getConfig') + ->willReturn( + [ + 'connection' => $connectionName + ] + ); + + $this->connectionMock->expects($this->once()) + ->method('query') + ->with($this->selectMock) + ->willReturn($this->statementMock); + + $this->iteratorFactoryMock->expects($this->once()) + ->method('create') + ->with($this->statementMock, null) + ->willReturn($this->iteratorMock); + $this->assertEquals($this->iteratorMock, $this->subject->getReport($reportName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php new file mode 100644 index 0000000000000..ce57a1eca3689 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php @@ -0,0 +1,257 @@ +resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->selectHydrator = $this->objectManagerHelper->getObject( + SelectHydrator::class, + [ + 'resourceConnection' => $this->resourceConnectionMock, + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + public function testExtract() + { + $selectParts = + [ + Select::DISTINCT, + Select::COLUMNS, + Select::UNION, + Select::FROM, + Select::WHERE, + Select::GROUP, + Select::HAVING, + Select::ORDER, + Select::LIMIT_COUNT, + Select::LIMIT_OFFSET, + Select::FOR_UPDATE + ]; + + $result = []; + foreach ($selectParts as $part) { + $result[$part] = "Part"; + } + $this->selectMock->expects($this->any()) + ->method('getPart') + ->willReturn("Part"); + $this->assertEquals($this->selectHydrator->extract($this->selectMock), $result); + } + + /** + * @dataProvider recreateWithoutExpressionDataProvider + * @param array $selectParts + * @param array $parts + * @param array $partValues + */ + public function testRecreateWithoutExpression($selectParts, $parts, $partValues) + { + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->selectMock); + foreach ($parts as $key => $part) { + $this->selectMock->expects($this->at($key)) + ->method('setPart') + ->with($part, $partValues[$key]); + } + + $this->assertSame($this->selectMock, $this->selectHydrator->recreate($selectParts)); + } + + /** + * @return array + */ + public function recreateWithoutExpressionDataProvider() + { + return [ + 'Select without expressions' => [ + [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + 'field_name_2', + 'alias_2', + ], + ] + ], + [Select::COLUMNS], + [[ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + 'field_name_2', + 'alias_2', + ], + ]], + ], + ]; + } + + /** + * @dataProvider recreateWithExpressionDataProvider + * @param array $selectParts + * @param array $expectedParts + * @param \PHPUnit_Framework_MockObject_MockObject[] $expressionMocks + */ + public function testRecreateWithExpression( + array $selectParts, + array $expectedParts, + array $expressionMocks + ) { + $this->objectManagerMock + ->expects($this->exactly(count($expressionMocks))) + ->method('create') + ->with($this->isType('string'), $this->isType('array')) + ->willReturnOnConsecutiveCalls(...$expressionMocks); + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnection') + ->with() + ->willReturn($this->connectionMock); + $this->connectionMock + ->expects($this->once()) + ->method('select') + ->with() + ->willReturn($this->selectMock); + foreach (array_keys($selectParts) as $key => $partName) { + $this->selectMock + ->expects($this->at($key)) + ->method('setPart') + ->with($partName, $expectedParts[$partName]); + } + + $this->assertSame($this->selectMock, $this->selectHydrator->recreate($selectParts)); + } + + /** + * @return array + */ + public function recreateWithExpressionDataProvider() + { + $expressionMock = $this->getMockBuilder(JsonSerializableExpression::class) + ->disableOriginalConstructor() + ->getMock(); + + return [ + 'Select without expressions' => [ + 'Parts' => [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + [ + 'class' => 'Some_class', + 'arguments' => [ + 'expression' => ['some(expression)'] + ] + ], + 'alias_2', + ], + ] + ], + 'expectedParts' => [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + $expressionMock, + 'alias_2', + ], + ] + ], + 'expectedExpressions' => [ + $expressionMock + ] + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json new file mode 100644 index 0000000000000..7edb72db45e52 --- /dev/null +++ b/app/code/Magento/Analytics/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-analytics", + "description": "N/A", + "require": { + "php": "~7.0.13|~7.1.0", + "magento/module-backend": "100.2.*", + "magento/module-config": "101.0.*", + "magento/module-integration": "100.2.*", + "magento/module-store": "100.2.*", + "magento/framework": "101.0.*" + }, + "type": "magento2-module", + "version": "100.2.3", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Analytics\\": "" + } + } +} diff --git a/app/code/Magento/Analytics/docs/images/M2_MA_signup.png b/app/code/Magento/Analytics/docs/images/M2_MA_signup.png new file mode 100644 index 0000000000000..78ed8fad92881 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/M2_MA_signup.png differ diff --git a/app/code/Magento/Analytics/docs/images/analytics_modules.png b/app/code/Magento/Analytics/docs/images/analytics_modules.png new file mode 100644 index 0000000000000..0bf6048b0d9cc Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/analytics_modules.png differ diff --git a/app/code/Magento/Analytics/docs/images/data_transition.png b/app/code/Magento/Analytics/docs/images/data_transition.png new file mode 100644 index 0000000000000..a75e97983e15d Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/data_transition.png differ diff --git a/app/code/Magento/Analytics/docs/images/definition.png b/app/code/Magento/Analytics/docs/images/definition.png new file mode 100644 index 0000000000000..16acc576320b0 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/definition.png differ diff --git a/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png b/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png new file mode 100644 index 0000000000000..f39d2e4900703 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png differ diff --git a/app/code/Magento/Analytics/docs/images/signup.png b/app/code/Magento/Analytics/docs/images/signup.png new file mode 100644 index 0000000000000..561e18b3a351f Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/signup.png differ diff --git a/app/code/Magento/Analytics/docs/images/update.png b/app/code/Magento/Analytics/docs/images/update.png new file mode 100644 index 0000000000000..149f5b5d3f9bd Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/update.png differ diff --git a/app/code/Magento/Analytics/docs/images/update_request.png b/app/code/Magento/Analytics/docs/images/update_request.png new file mode 100644 index 0000000000000..7181251e3634e Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/update_request.png differ diff --git a/app/code/Magento/Analytics/etc/acl.xml b/app/code/Magento/Analytics/etc/acl.xml new file mode 100644 index 0000000000000..bf2251895f929 --- /dev/null +++ b/app/code/Magento/Analytics/etc/acl.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/adminhtml/di.xml b/app/code/Magento/Analytics/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..5e305e70e5ad3 --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/di.xml @@ -0,0 +1,16 @@ + + + + + + + Magento\Analytics\Model\System\Message\NotificationAboutFailedSubscription + + + + diff --git a/app/code/Magento/Analytics/etc/adminhtml/menu.xml b/app/code/Magento/Analytics/etc/adminhtml/menu.xml new file mode 100644 index 0000000000000..915211c4bb85e --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/menu.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/adminhtml/routes.xml b/app/code/Magento/Analytics/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..0ae2762dacc5f --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..c7da840b7e665 --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -0,0 +1,53 @@ + + + + +
+ + general + Magento_Analytics::analytics_settings + + + For more information, see our + terms and conditions.]]> + + + Magento\Config\Model\Config\Source\Enabledisable + Magento\Analytics\Model\Config\Backend\Enabled + Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel + analytics/subscription/enabled + + + + Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel + Magento\Analytics\Model\Config\Backend\CollectionTime + + + Industry Data + + In order to personalize your Advanced Reporting experience, please select your industry. + Magento\Analytics\Model\Config\Source\Vertical + Magento\Analytics\Model\Config\Backend\Vertical + Magento\Analytics\Block\Adminhtml\System\Config\Vertical + + 1 + + + + + Learn more about Magento BI Essentials and BI Pro tiers.]]> + Magento\Analytics\Block\Adminhtml\System\Config\AdditionalComment + + +
+
+
diff --git a/app/code/Magento/Analytics/etc/analytics.xml b/app/code/Magento/Analytics/etc/analytics.xml new file mode 100644 index 0000000000000..77ebe751a31cf --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xml @@ -0,0 +1,50 @@ + + + + + + + + modules + + + + + + + + + + + + + + stores + + + + + + + + + websites + + + + + + + + + groups + + + + + diff --git a/app/code/Magento/Analytics/etc/analytics.xsd b/app/code/Magento/Analytics/etc/analytics.xsd new file mode 100644 index 0000000000000..2506e3d6a6a9a --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xsd @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + File name attribute can has only [a-zA-Z0-9/_]. + + + + + + + + + + Value is required. + + + + + + + diff --git a/app/code/Magento/Analytics/etc/config.xml b/app/code/Magento/Analytics/etc/config.xml new file mode 100644 index 0000000000000..b6194ba12993f --- /dev/null +++ b/app/code/Magento/Analytics/etc/config.xml @@ -0,0 +1,25 @@ + + + + + + + https://advancedreporting.rjmetrics.com/signup + https://advancedreporting.rjmetrics.com/update + https://dashboard.rjmetrics.com/v2/magento/signup + https://advancedreporting.rjmetrics.com/otp + https://advancedreporting.rjmetrics.com/report + https://advancedreporting.rjmetrics.com/report + + Magento Analytics user + + 02,00,00 + + + + diff --git a/app/code/Magento/Analytics/etc/crontab.xml b/app/code/Magento/Analytics/etc/crontab.xml new file mode 100644 index 0000000000000..a4beef0359540 --- /dev/null +++ b/app/code/Magento/Analytics/etc/crontab.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/di.xml b/app/code/Magento/Analytics/etc/di.xml new file mode 100644 index 0000000000000..b9bb9cc9ff00c --- /dev/null +++ b/app/code/Magento/Analytics/etc/di.xml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + Magento\Analytics\Model\Connector\SignUpCommand + Magento\Analytics\Model\Connector\UpdateCommand + Magento\Analytics\Model\Connector\NotifyDataChangedCommand + + + + + + + Magento\Analytics\ReportXml\Config\Data + + + + + Magento\Analytics\ReportXml\Config\Reader + Magento_Analytics_ReportXml_CacheId + + + + + urn:magento:module:Magento_Analytics:etc/reports.xsd + + + + + Magento\Analytics\ReportXml\Config\Converter\Xml + Magento\Analytics\ReportXml\Config\SchemaLocator + reports.xml + + name + + name + alias + + name + name + + glue + + attribute + operator + + + glue + + attribute + operator + + + glue + + attribute + operator + + glue + + attribute + operator + + + + + + + + Magento\Analytics\ReportXml\Config\Reader\Xml + + + + + + + Magento\Analytics\Model\Config\Data + + + + + Magento\Analytics\Model\Config\Reader + Magento_Analytics_CacheId + + + + + urn:magento:module:Magento_Analytics:etc/analytics.xsd + + + + + Magento\Analytics\ReportXml\Config\Converter\Xml + Magento\Analytics\Model\Config\SchemaLocator + analytics.xml + + name + + + + + + + + Magento\Analytics\ReportXml\DB\Assembler\FromAssembler + Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler + Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler + + + + + + + Magento\Analytics\Model\Config\Reader\Xml + + + + + + + web/unsecure/base_url + currency/options/base + general/locale/timezone + general/country/default + carriers/dhl/title + carriers/dhl/active + carriers/fedex/title + carriers/fedex/active + carriers/flatrate/title + carriers/flatrate/active + carriers/tablerate/title + carriers/tablerate/active + carriers/freeshipping/title + carriers/freeshipping/active + carriers/ups/title + carriers/ups/active + carriers/usps/title + carriers/usps/active + payment/free/title + payment/free/active + payment/checkmo/title + payment/checkmo/active + payment/purchaseorder/title + payment/purchaseorder/active + payment/banktransfer/title + payment/banktransfer/active + payment/cashondelivery/title + payment/cashondelivery/active + payment/authorizenet_directpost/title + payment/authorizenet_directpost/active + payment/paypal_billing_agreement/title + payment/paypal_billing_agreement/active + payment/braintree/title + payment/braintree/active + payment/braintree_paypal/title + payment/braintree_paypal/active + analytics/general/vertical + + + + + + + Apps and Games + Athletic/Sporting Goods + Art and Design + Auto Parts + Baby/Children’s Apparel, Gear and Toys + Beauty and Cosmetics + Books, Music and Magazines + Crafts and Stationery + Consumer Electronics + Deal Site + Fashion Apparel and Accessories + Food, Beverage and Grocery + Home Goods and Furniture + Home Improvement + Jewelry and Watches + Mass Merchant + Office Supplies + Outdoor and Camping Gear + Pet Goods + Pharma and Medical Devices + Technology B2B + Other + + + + + + + + + + \Magento\Analytics\Model\Connector\ResponseHandler\SignUp + + + + + + + Magento\Analytics\Model\Connector\ResponseHandler\Update + Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp + + + + + + + Magento\Analytics\Model\Connector\ResponseHandler\OTP + Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp + + + + + + + Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp + + + + + + SignUpResponseResolver + + + + + UpdateResponseResolver + + + + + OtpResponseResolver + + + + + NotifyDataChangedResponseResolver + + + + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + 1 + + + + diff --git a/app/code/Magento/Analytics/etc/module.xml b/app/code/Magento/Analytics/etc/module.xml new file mode 100644 index 0000000000000..32ee5d23a4d86 --- /dev/null +++ b/app/code/Magento/Analytics/etc/module.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/reports.xml b/app/code/Magento/Analytics/etc/reports.xml new file mode 100644 index 0000000000000..8a43658670293 --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/Analytics/etc/reports.xsd b/app/code/Magento/Analytics/etc/reports.xsd new file mode 100644 index 0000000000000..d0ba4068244fe --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xsd @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Analytics/etc/webapi.xml b/app/code/Magento/Analytics/etc/webapi.xml new file mode 100644 index 0000000000000..8252d039f1d03 --- /dev/null +++ b/app/code/Magento/Analytics/etc/webapi.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/app/code/Magento/Analytics/i18n/en_US.csv b/app/code/Magento/Analytics/i18n/en_US.csv new file mode 100644 index 0000000000000..516c388feb823 --- /dev/null +++ b/app/code/Magento/Analytics/i18n/en_US.csv @@ -0,0 +1,84 @@ +"Subscription status","Subscription status" +"Sorry, there has been an error processing your request. Please try again later.","Sorry, there has been an error processing your request. Please try again later." +"Sorry, there was an error processing your registration request to Magento Analytics. Please try again later.","Sorry, there was an error processing your registration request to Magento Analytics. Please try again later." +"Error occurred during postponement notification","Error occurred during postponement notification" +"Time value has an unsupported format","Time value has an unsupported format" +"Cron settings can't be saved","Cron settings can't be saved" +"There was an error save new configuration value.","There was an error save new configuration value." +"Please select a vertical.","Please select a vertical." +"--Please Select--","--Please Select--" +"Command was not found.","Command was not found." +"Input data must be string or convertible into string.","Input data must be string or convertible into string." +"Input data must be non-empty string.","Input data must be non-empty string." +"Not valid cipher method.","Not valid cipher method." +"Encryption key can't be empty.","Encryption key can't be empty." +"Source ""%1"" is not exist","Source ""%1"" is not exist" +"These arguments can't be empty ""%1""","These arguments can't be empty ""%1""" +"Cannot find predefined integration user!","Cannot find predefined integration user!" +"File is not ready yet.","File is not ready yet." +"Your Base URL has been changed and your reports are being updated. Advanced Reporting will be available once this change has been processed. Please try again later.","Your Base URL has been changed and your reports are being updated. Advanced Reporting will be available once this change has been processed. Please try again later." +"Failed to synchronize data to the Magento Business Intelligence service. ","Failed to synchronize data to the Magento Business Intelligence service. " +"Retry Synchronization","Retry Synchronization" +TestMessage,TestMessage +"Error message","Error message" +"Apps and Games","Apps and Games" +"Athletic/Sporting Goods","Athletic/Sporting Goods" +"Art and Design","Art and Design" +"Advanced Reporting","Advanced Reporting" +"Gain new insights and take command of your business' performance, using our dynamic product, order, and customer reports tailored to your customer data.","Gain new insights and take command of your business' performance, using our dynamic product, order, and customer reports tailored to your customer data." +"View details","View details" +"Go to Advanced Reporting","Go to Advanced Reporting" +"An error occurred while subscription process.","An error occurred while subscription process." +Analytics,Analytics +API,API +Configuration,Configuration +"Business Intelligence","Business Intelligence" +"BI Essentials","BI Essentials" +"This service provides a dynamic suite of reports with rich insights about your business. + Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the + ""Go to Advanced Reporting"" link.
For more information, see our + terms and conditions. + ","This service provides a dynamic suite of reports with rich insights about your business. + Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the + ""Go to Advanced Reporting"" link.
For more information, see our + terms and conditions." +"Advanced Reporting Service","Advanced Reporting Service" +Industry,Industry +"Time of day to send data","Time of day to send data" +"Get more insights from Magento Business Intelligence","Get more insights from Magento Business Intelligence" +"Magento Business Intelligence provides you with a simple and clear path to + becoming more data driven.
Learn more about BI Essentials tier.","Magento Business Intelligence provides you with a simple and clear path to + becoming more data driven.
Learn more about BI Essentials tier." +"Auto Parts","Auto Parts" +"Baby/Children’s Apparel, Gear and Toys","Baby/Children’s Apparel, Gear and Toys" +"Beauty and Cosmetics","Beauty and Cosmetics" +"Books, Music and Magazines","Books, Music and Magazines" +"Crafts and Stationery","Crafts and Stationery" +"Consumer Electronics","Consumer Electronics" +"Deal Site","Deal Site" +"Fashion Apparel and Accessories","Fashion Apparel and Accessories" +"Food, Beverage and Grocery","Food, Beverage and Grocery" +"Home Goods and Furniture","Home Goods and Furniture" +"Home Improvement","Home Improvement" +"Jewelry and Watches","Jewelry and Watches" +"Mass Merchant","Mass Merchant" +"Office Supplies","Office Supplies" +"Outdoor and Camping Gear","Outdoor and Camping Gear" +"Pet Goods","Pet Goods" +"Pharma and Medical Devices","Pharma and Medical Devices" +"Technology B2B","Technology B2B" +"Analytics Subscription","Analytics Subscription" +"powered by Magento Business Intelligence","powered by Magento Business Intelligence" +"Are you sure you want to opt out?","Are you sure you want to opt out?" +Cancel,Cancel +"Opt out","Opt out" +"

Advanced Reporting in included, + free of charge, in your Magento software. When you opt out, we collect no product, order, and + customer data to generate our dynamic reports.

To opt in later: You can always turn on Advanced + Reporting in you Admin Panel.

","

Advanced Reporting in included, + free of charge, in your Magento software. When you opt out, we collect no product, order, and + customer data to generate our dynamic reports.

To opt in later: You can always turn on Advanced + Reporting in you Admin Panel.

" +"In order to personalize your Advanced Reporting experience, please select your industry.","In order to personalize your Advanced Reporting experience, please select your industry." diff --git a/app/code/Magento/Analytics/registration.php b/app/code/Magento/Analytics/registration.php new file mode 100644 index 0000000000000..58d3688b7491d --- /dev/null +++ b/app/code/Magento/Analytics/registration.php @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml new file mode 100644 index 0000000000000..a22c603b2a8b3 --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml @@ -0,0 +1,28 @@ + + +
+
+
+ escapeHtml(__('Advanced Reporting')) ?> +
+
+ escapeHtml(__('Gain new insights and take command of your business\' performance,' . + ' using our dynamic product, order, and customer reports tailored to your customer data.')) ?> +
+
+ +
diff --git a/app/code/Magento/Authorization/Test/Mftf/LICENSE.txt b/app/code/Magento/Authorization/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Authorization/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Authorization/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/Authorization/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Authorization/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/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..58720ad9c9f5c 100644 --- a/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php +++ b/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php @@ -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 a894193888100..35063d1516784 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -2,12 +2,12 @@ "name": "magento/module-authorization", "description": "Authorization module provides access to Magento ACL functionality.", "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "php": "~7.0.13|~7.1.0", "magento/module-backend": "100.2.*", - "magento/framework": "100.2.*" + "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.0-dev", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index 0f10fd633cb5b..9186acce83b4c 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 @@ -141,11 +150,12 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra * @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( @@ -162,7 +172,7 @@ public function __construct( \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory, TransactionService $transactionService, - ZendClientFactory $httpClientFactory, + \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/Test/Mftf/LICENSE.txt b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Authorizenet/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Authorizenet/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/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/Model/Directpost/ResponseTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/Directpost/ResponseTest.php index 6e5d55e52675e..b4274e87401ca 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,14 @@ 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..26d96b9bc2d90 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) { $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 2abfebc3c1cb7..90f19e36777b2 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -2,21 +2,21 @@ "name": "magento/module-authorizenet", "description": "N/A", "require": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/module-sales": "100.2.*", + "php": "~7.0.13|~7.1.0", + "magento/module-sales": "101.0.*", "magento/module-store": "100.2.*", - "magento/module-quote": "100.2.*", + "magento/module-quote": "101.0.*", "magento/module-checkout": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-payment": "100.2.*", - "magento/module-catalog": "101.1.*", - "magento/framework": "100.2.*" + "magento/module-catalog": "102.0.*", + "magento/framework": "101.0.*" }, "suggest": { - "magento/module-config": "100.2.*" + "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.0-dev", + "version": "100.2.2", "license": [ "proprietary" ], diff --git a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js index 8edc38dce6f60..8c4c90bf111de 100644 --- a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js +++ b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js @@ -6,7 +6,8 @@ var config = { map: { '*': { - transparent: 'Magento_Payment/transparent' + transparent: 'Magento_Payment/js/transparent', + 'Magento_Payment/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..3f658ee90bf4e 100644 --- a/app/code/Magento/Backend/App/AbstractAction.php +++ b/app/code/Magento/Backend/App/AbstractAction.php @@ -217,6 +217,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 +227,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); } 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/Config.php b/app/code/Magento/Backend/App/Config.php index bb60a1e69dd47..e0983139a690d 100644 --- a/app/code/Magento/Backend/App/Config.php +++ b/app/code/Magento/Backend/App/Config.php @@ -68,6 +68,6 @@ public function isSetFlag($path) if ($path) { $configPath .= '/' . $path; } - return (bool) $this->appConfig->get(System::CONFIG_TYPE, $configPath); + return (bool)$this->appConfig->get(System::CONFIG_TYPE, $configPath); } } 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/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 @@ +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 cecd7b8050352..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 @@ -421,6 +421,8 @@ public function getChartUrl($directUrl = true) $tmpstring = implode('|', $this->_axisLabels[$idx]); $valueBuffer[] = $indexid . ":|" . $tmpstring; + } elseif ($idx == 'y') { + $valueBuffer[] = $indexid . ":|" . implode('|', $yLabels); } $indexid++; } 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/DataProviders/UploadConfig.php b/app/code/Magento/Backend/Block/DataProviders/UploadConfig.php new file mode 100644 index 0000000000000..5c66ccff0826b --- /dev/null +++ b/app/code/Magento/Backend/Block/DataProviders/UploadConfig.php @@ -0,0 +1,39 @@ +config = $config; + } + + /** + * Get image resize configuration + * + * @return int + */ + public function getIsResizeEnabled(): int + { + return (int)$this->config->getValue('system/upload_configuration/enable_resize'); + } +} diff --git a/app/code/Magento/Backend/Block/GlobalSearch.php b/app/code/Magento/Backend/Block/GlobalSearch.php index f4a46283808f4..b45eb84cdaee9 100644 --- a/app/code/Magento/Backend/Block/GlobalSearch.php +++ b/app/code/Magento/Backend/Block/GlobalSearch.php @@ -3,19 +3,61 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block; +use Magento\Backend\Model\GlobalSearch\SearchEntityFactory; +use Magento\Backend\Model\GlobalSearch\SearchEntity; +use Magento\Framework\App\ObjectManager; + /** * @api * @since 100.0.2 */ class GlobalSearch extends \Magento\Backend\Block\Template { + /** + * @var SearchEntityFactory + */ + private $searchEntityFactory; + /** * @var string */ protected $_template = 'Magento_Backend::system/search.phtml'; + /** + * @var array + */ + private $entityResources; + + /** + * @var array + */ + private $entityPaths; + + /** + * @param Template\Context $context + * @param array $data + * @param array $entityResources + * @param array $entityPaths + * @param SearchEntityFactory|null $searchEntityFactory + */ + public function __construct( + Template\Context $context, + array $data = [], + array $entityResources = [], + array $entityPaths = [], + SearchEntityFactory $searchEntityFactory = null + ) { + $this->entityResources = $entityResources; + $this->entityPaths = $entityPaths; + $this->searchEntityFactory = $searchEntityFactory ?: ObjectManager::getInstance() + ->get(SearchEntityFactory::class); + + parent::__construct($context, $data); + } + /** * Get components configuration * @return array @@ -31,7 +73,52 @@ public function getWidgetInitOptions() 'filterProperty' => 'name', 'preventClickPropagation' => false, 'minLength' => 2, + 'submitInputOnEnter' => false, ] ]; } + + /** + * Get entities which are allowed to show. + * + * @return SearchEntity[] + */ + public function getEntitiesToShow() + { + $allowedEntityTypes = []; + $entitiesToShow = []; + + foreach ($this->entityResources as $entityType => $resource) { + if ($this->getAuthorization()->isAllowed($resource)) { + $allowedEntityTypes[] = $entityType; + } + } + + foreach ($allowedEntityTypes as $entityType) { + $url = $this->getUrlEntityType($entityType); + + $searchEntity = $this->searchEntityFactory->create(); + $searchEntity->setId('searchPreview' . $entityType); + $searchEntity->setTitle('in ' . $entityType); + $searchEntity->setUrl($url); + + $entitiesToShow[] = $searchEntity; + } + + return $entitiesToShow; + } + + /** + * Get url path by entity type. + * + * @param string $entityType + * + * @return string + */ + private function getUrlEntityType(string $entityType) + { + $urlPath = $this->entityPaths[$entityType] ?? ''; + + return $this->getUrl($urlPath); + } } diff --git a/app/code/Magento/Backend/Block/Media/Uploader.php b/app/code/Magento/Backend/Block/Media/Uploader.php index 4987cb248bd0b..7e1ad03470720 100644 --- a/app/code/Magento/Backend/Block/Media/Uploader.php +++ b/app/code/Magento/Backend/Block/Media/Uploader.php @@ -3,8 +3,14 @@ * 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\ConfigInterface; + /** * Adminhtml media library uploader * @api @@ -27,17 +33,34 @@ class Uploader extends \Magento\Backend\Block\Widget */ protected $_fileSizeService; + /** + * @var Json + */ + private $jsonEncoder; + + /** + * @var ConfigInterface + */ + private $imageConfig; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\File\Size $fileSize * @param array $data + * @param Json $jsonEncoder + * @param ConfigInterface $imageConfig */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\File\Size $fileSize, - array $data = [] + array $data = [], + Json $jsonEncoder = null, + ConfigInterface $imageConfig = null ) { $this->_fileSizeService = $fileSize; + $this->jsonEncoder = $jsonEncoder ?: ObjectManager::getInstance()->get(Json::class); + $this->imageConfig = $imageConfig ?: ObjectManager::getInstance()->get(ConfigInterface::class); + parent::__construct($context, $data); } @@ -79,6 +102,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 * @@ -107,7 +150,7 @@ public function getJsObjectName() */ public function getConfigJson() { - return $this->_coreData->jsonEncode($this->getConfig()->getData()); + return $this->jsonEncoder->encode($this->getConfig()->getData()); } /** diff --git a/app/code/Magento/Backend/Block/Menu.php b/app/code/Magento/Backend/Block/Menu.php index bb239d31b1779..eae066b07ac38 100644 --- a/app/code/Magento/Backend/Block/Menu.php +++ b/app/code/Magento/Backend/Block/Menu.php @@ -76,6 +76,11 @@ class Menu extends \Magento\Backend\Block\Template */ private $anchorRenderer; + /** + * @var ConfigInterface + */ + private $routeConfig; + /** * @param Template\Context $context * @param \Magento\Backend\Model\UrlInterface $url @@ -83,9 +88,11 @@ class Menu extends \Magento\Backend\Block\Template * @param \Magento\Backend\Model\Auth\Session $authSession * @param \Magento\Backend\Model\Menu\Config $menuConfig * @param \Magento\Framework\Locale\ResolverInterface $localeResolver + * @param \Magento\Framework\App\Route\ConfigInterface $routeConfig * @param array $data * @param MenuItemChecker|null $menuItemChecker * @param AnchorRenderer|null $anchorRenderer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -96,7 +103,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; @@ -105,6 +113,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); } @@ -203,8 +214,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[2], $match[3] ); 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/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())) . - ''; + '
' + . '(' . __('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())) . - ''; + '
' . + '(' . __('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())) . - ''; + '
' . + '(' . __('Code') . ': ' . $row->getCode() . ')'; } } diff --git a/app/code/Magento/Backend/Block/Template.php b/app/code/Magento/Backend/Block/Template.php index d0f39b54c1492..477be0f82462b 100644 --- a/app/code/Magento/Backend/Block/Template.php +++ b/app/code/Magento/Backend/Block/Template.php @@ -17,10 +17,12 @@ * Example: * * - * My\Module\ViewModel\Custom + * My\Module\ViewModel\Custom * * * + * 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..e2d71d171cf15 100644 --- a/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php +++ b/app/code/Magento/Backend/Block/Widget/Button/ButtonList.php @@ -127,12 +127,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..d288dc1c5d43e 100644 --- a/app/code/Magento/Backend/Block/Widget/Form.php +++ b/app/code/Magento/Backend/Block/Widget/Form.php @@ -3,8 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Block\Widget; +use Magento\Backend\Block\Widget\Form\Element\ElementCreator; +use Magento\Framework\App\ObjectManager; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Data\Form as DataForm; +use Magento\Backend\Block\Widget\Form\Renderer\Element; +use Magento\Backend\Block\Widget\Form\Renderer\Fieldset; +use Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element as FieldsetElement; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Data\Form\AbstractForm; + /** * Backend form widget * @@ -18,7 +30,7 @@ class Form extends \Magento\Backend\Block\Widget /** * Form Object * - * @var \Magento\Framework\Data\Form + * @var DataForm */ protected $_form; @@ -28,12 +40,24 @@ class Form extends \Magento\Backend\Block\Widget protected $_template = 'Magento_Backend::widget/form.phtml'; /** - * @param \Magento\Backend\Block\Template\Context $context + * @var ElementCreator + * / + private $creator; + + /** + * Constructs form + * + * @param Context $context * @param array $data + * @param ElementCreator|null $creator */ - public function __construct(\Magento\Backend\Block\Template\Context $context, array $data = []) - { + public function __construct( + Context $context, + array $data = [], + ElementCreator $creator = null + ) { parent::__construct($context, $data); + $this->creator = $creator ?: ObjectManager::getInstance()->get(ElementCreator::class); } /** @@ -58,21 +82,21 @@ protected function _construct() */ protected function _prepareLayout() { - \Magento\Framework\Data\Form::setElementRenderer( + DataForm::setElementRenderer( $this->getLayout()->createBlock( - \Magento\Backend\Block\Widget\Form\Renderer\Element::class, + Element::class, $this->getNameInLayout() . '_element' ) ); - \Magento\Framework\Data\Form::setFieldsetRenderer( + DataForm::setFieldsetRenderer( $this->getLayout()->createBlock( - \Magento\Backend\Block\Widget\Form\Renderer\Fieldset::class, + Fieldset::class, $this->getNameInLayout() . '_fieldset' ) ); - \Magento\Framework\Data\Form::setFieldsetElementRenderer( + DataForm::setFieldsetElementRenderer( $this->getLayout()->createBlock( - \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element::class, + FieldsetElement::class, $this->getNameInLayout() . '_fieldset_element' ) ); @@ -83,7 +107,7 @@ protected function _prepareLayout() /** * Get form object * - * @return \Magento\Framework\Data\Form + * @return DataForm */ public function getForm() { @@ -106,10 +130,10 @@ public function getFormHtml() /** * Set form object * - * @param \Magento\Framework\Data\Form $form + * @param DataForm $form * @return $this */ - public function setForm(\Magento\Framework\Data\Form $form) + public function setForm(DataForm $form) { $this->_form = $form; $this->_form->setParent($this); @@ -148,6 +172,7 @@ protected function _beforeToHtml() /** * Initialize form fields values + * * Method will be called after prepareForm and can be used for field values initialization * * @return $this @@ -169,36 +194,15 @@ protected function _setFieldset($attributes, $fieldset, $exclude = []) { $this->_addElementTypes($fieldset); foreach ($attributes as $attribute) { - /* @var $attribute \Magento\Eav\Model\Entity\Attribute */ + /* @var $attribute Attribute */ 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); @@ -209,10 +213,10 @@ protected function _setFieldset($attributes, $fieldset, $exclude = []) /** * Check whether attribute is visible * - * @param \Magento\Eav\Model\Entity\Attribute $attribute + * @param Attribute $attribute * @return bool */ - protected function _isAttributeVisible(\Magento\Eav\Model\Entity\Attribute $attribute) + protected function _isAttributeVisible(Attribute $attribute) { return !(!$attribute || $attribute->hasIsVisible() && !$attribute->getIsVisible()); } @@ -221,11 +225,11 @@ protected function _isAttributeVisible(\Magento\Eav\Model\Entity\Attribute $attr * Apply configuration specific for different element type * * @param string $inputType - * @param \Magento\Framework\Data\Form\Element\AbstractElement $element - * @param \Magento\Eav\Model\Entity\Attribute $attribute + * @param AbstractElement $element + * @param Attribute $attribute * @return void */ - protected function _applyTypeSpecificConfig($inputType, $element, \Magento\Eav\Model\Entity\Attribute $attribute) + protected function _applyTypeSpecificConfig($inputType, $element, Attribute $attribute) { switch ($inputType) { case 'select': @@ -249,10 +253,10 @@ protected function _applyTypeSpecificConfig($inputType, $element, \Magento\Eav\M /** * Add new element type * - * @param \Magento\Framework\Data\Form\AbstractForm $baseElement + * @param AbstractForm $baseElement * @return void */ - protected function _addElementTypes(\Magento\Framework\Data\Form\AbstractForm $baseElement) + protected function _addElementTypes(AbstractForm $baseElement) { $types = $this->_getAdditionalElementTypes(); foreach ($types as $code => $className) { @@ -273,7 +277,7 @@ protected function _getAdditionalElementTypes() /** * Render additional element * - * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param AbstractElement $element * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ 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 ''; + + $params = $this->_getDependsJson(); + + if ($this->_configOptions) { + $params .= ', ' . $this->_jsonEncoder->encode($this->_configOptions); + } + + return ""; } /** 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..c81f8dc369242 --- /dev/null +++ b/app/code/Magento/Backend/Block/Widget/Form/Element/ElementCreator.php @@ -0,0 +1,134 @@ +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/AbstractRenderer.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php index b8a2e283b29a0..9df8c532adfdd 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php @@ -58,7 +58,7 @@ public function render(DataObject $row) $result .= $this->getColumn()->getEditOnly() ? '' : '' . $this->_getValue($row) . ''; - return $result . $this->_getInputValueElement($row) . '' ; + return $result . $this->_getInputValueElement($row) . ''; } return $this->_getValue($row); } 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 = '