diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 92a51eb84a4ab..89aea7299855c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,18 +1,23 @@ # 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/). +This contribution model has contributors maintaining their own fork of the Magento 2 repository. +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. +The Magento 2 development team or community maintainers 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. +For more detailed information on contribution please read our [beginners guide](https://github.com/magento/magento2/wiki/Getting-Started). ## Contribution requirements -1. Contributions must adhere to the [Magento coding standards](https://devdocs.magento.com/guides/v2.2/coding-standards/bk-coding-standards.html). +1. Contributions must adhere to the [Magento coding standards](https://devdocs.magento.com/guides/v2.3/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. +3. Commits must be accompanied by meaningful commit messages. Please see the [Magento Pull Request Template](https://github.com/magento/magento2/blob/2.3-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 @@ -22,15 +27,22 @@ The Magento 2 development team will review all issues and contributions submitte ## 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. +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](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#pull_request). +4. Fork the Magento 2 repository according to the [Fork A Repository instructions](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.3/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). + +## Connecting with Community! + +If you have any questions, join us in [#beginners](https://magentocommeng.slack.com/messages/CH8BGFX9D) Slack chat. If you are not on our slack, [click here](http://tinyurl.com/engcom-slack) to join. + +Need to find a project? Check out the [Slack Channels](https://github.com/magento/magento2/wiki/Slack-Channels) (with listed project info) and the [Magento Community Portal](https://opensource.magento.com/). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f191bd9aaba67..11da06ee704c6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,7 +21,6 @@ There could be 1 or more issues linked here and it will help us find some more information about the reasoning behind this change. --> 1. magento/magento2#: Issue title -2. ... ### Manual testing scenarios (*) + ### 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) + - [ ] All automated tests passed successfully (all builds are green) diff --git a/.travis.yml b/.travis.yml.sample similarity index 100% rename from .travis.yml rename to .travis.yml.sample diff --git a/CHANGELOG.md b/CHANGELOG.md index 04fb46a825f62..59adb577b0cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,415 @@ +2.3.3 +============= +* GitHub issues: + * [#533](https://github.com/magento/magento2/issues/533) -- By default Allow all access in .htaccess (fixed in [magento/graphql-ce#578](https://github.com/magento/graphql-ce/pull/578)) + * [#601](https://github.com/magento/magento2/issues/601) -- Admin tabs js error. (fixed in [magento/graphql-ce#632](https://github.com/magento/graphql-ce/pull/632)) + * [#631](https://github.com/magento/magento2/issues/631) -- Image Management / Editing (fixed in [magento/graphql-ce#634](https://github.com/magento/graphql-ce/pull/634)) + * [#20124](https://github.com/magento/magento2/issues/20124) -- Sort By label is hidden by Shop By Menu on listing page in iphone5 device (fixed in [magento/magento2#20135](https://github.com/magento/magento2/pull/20135)) + * [#21978](https://github.com/magento/magento2/issues/21978) -- Adding product image: File doesn't exist (fixed in [magento/magento2#22020](https://github.com/magento/magento2/pull/22020)) + * [#22045](https://github.com/magento/magento2/issues/22045) -- Instant Purchase on product page not working properly. (fixed in [magento/magento2#22260](https://github.com/magento/magento2/pull/22260)) + * [#22134](https://github.com/magento/magento2/issues/22134) -- Paypal buttons disable issue - Magento 2.3.1 (fixed in [magento/magento2#22260](https://github.com/magento/magento2/pull/22260)) + * [#22249](https://github.com/magento/magento2/issues/22249) -- Configurable Product Gallery Images Out of Order when More than 10 images (fixed in [magento/magento2#22287](https://github.com/magento/magento2/pull/22287)) + * [#22527](https://github.com/magento/magento2/issues/22527) -- Wishlist and compare icon align issue in product listing page (fixed in [magento/magento2#22532](https://github.com/magento/magento2/pull/22532)) + * [#628](https://github.com/magento/magento2/issues/628) -- Feature Request: Parent entities links in child entities grid. (fixed in [magento/graphql-ce#636](https://github.com/magento/graphql-ce/pull/636)) + * [#640](https://github.com/magento/magento2/issues/640) -- [Insight] Files should not be executable (fixed in [magento/graphql-ce#648](https://github.com/magento/graphql-ce/pull/648)) + * [#603](https://github.com/magento/magento2/issues/603) -- 'Continue' button is disabled even though "I've read OSL licence" is checked (fixed in [magento/graphql-ce#653](https://github.com/magento/graphql-ce/pull/653)) + * [#22406](https://github.com/magento/magento2/issues/22406) -- Store view specific labels cut in left navigation menu (fixed in [magento/magento2#22423](https://github.com/magento/magento2/pull/22423)) + * [#19515](https://github.com/magento/magento2/issues/19515) -- Create new order from backend saves the credit card when it is told not to (fixed in [magento/magento2#19767](https://github.com/magento/magento2/pull/19767)) + * [#21473](https://github.com/magento/magento2/issues/21473) -- Form element validation is not triggered when validation rules change (fixed in [magento/magento2#21992](https://github.com/magento/magento2/pull/21992)) + * [#22641](https://github.com/magento/magento2/issues/22641) -- Typo Issue and Missing header title at Customer Sales order grid (fixed in [magento/magento2#22643](https://github.com/magento/magento2/pull/22643)) + * [#22647](https://github.com/magento/magento2/issues/22647) -- In customer account create page word not readable, should use '-' after break to new line In mobile view (fixed in [magento/magento2#22656](https://github.com/magento/magento2/pull/22656)) + * [#22395](https://github.com/magento/magento2/issues/22395) -- config:set -le and -lc short form options don't work (fixed in [magento/magento2#22720](https://github.com/magento/magento2/pull/22720)) + * [#198](https://github.com/magento/magento2/issues/198) -- Better Search Please!! NOW (fixed in [magento/graphql-ce#371](https://github.com/magento/graphql-ce/pull/371)) + * [#436](https://github.com/magento/magento2/issues/436) -- [Feature request]custom option attach an image (fixed in [magento/graphql-ce#445](https://github.com/magento/graphql-ce/pull/445)) + * [#309](https://github.com/magento/magento2/issues/309) -- Terrible UI in Backend (fixed in [magento/graphql-ce#504](https://github.com/magento/graphql-ce/pull/504)) + * [#535](https://github.com/magento/magento2/issues/535) -- A few bugs (fixed in [magento/graphql-ce#650](https://github.com/magento/graphql-ce/pull/650)) + * [#658](https://github.com/magento/magento2/issues/658) -- Inline translate malfunctioning (fixed in [magento/graphql-ce#665](https://github.com/magento/graphql-ce/pull/665) and [magento/graphql-ce#744](https://github.com/magento/graphql-ce/pull/744)) + * [#657](https://github.com/magento/magento2/issues/657) -- Feature Request: Grid paging options at the top and the bottom of the grid. (fixed in [magento/graphql-ce#666](https://github.com/magento/graphql-ce/pull/666)) + * [#12612](https://github.com/magento/magento2/issues/12612) -- Array to String conversion error on checkout page when changin country - how to debug (fixed in [magento/magento2#22558](https://github.com/magento/magento2/pull/22558)) + * [#22556](https://github.com/magento/magento2/issues/22556) -- VatValidator::validate returns error if region in quoteAddress is not set (fixed in [magento/magento2#22558](https://github.com/magento/magento2/pull/22558)) + * [#20843](https://github.com/magento/magento2/issues/20843) -- Uncaught TypeError: panel.addClass is not a function when Swatches are disabled (fixed in [magento/magento2#22560](https://github.com/magento/magento2/pull/22560)) + * [#22636](https://github.com/magento/magento2/issues/22636) -- arrow toggle not changing only showing to down It should be toggle as every where is working (fixed in [magento/magento2#22644](https://github.com/magento/magento2/pull/22644)) + * [#22640](https://github.com/magento/magento2/issues/22640) -- Add tax rule form checkbox design is not as per the magento admin panel checkbox design, It is showing default design (fixed in [magento/magento2#22655](https://github.com/magento/magento2/pull/22655)) + * [#20906](https://github.com/magento/magento2/issues/20906) -- Magento backend catalog "Cost" without currency symbol (fixed in [magento/magento2#22739](https://github.com/magento/magento2/pull/22739)) + * [#22771](https://github.com/magento/magento2/issues/22771) -- Magento 2.3.0 can't change text area field height admin form using Ui component (fixed in [magento/magento2#22779](https://github.com/magento/magento2/pull/22779)) + * [#22788](https://github.com/magento/magento2/issues/22788) -- New Shipment emails do not generate (fixed in [magento/magento2#22791](https://github.com/magento/magento2/pull/22791)) + * [#18651](https://github.com/magento/magento2/issues/18651) -- Tierprice can't save float percentage value (fixed in [magento/magento2#19584](https://github.com/magento/magento2/pull/19584)) + * [#21672](https://github.com/magento/magento2/issues/21672) -- Database Media Storage - Design Config fails to save transactional email logo correctly (fixed in [magento/magento2#21675](https://github.com/magento/magento2/pull/21675) and [magento/magento2#21674](https://github.com/magento/magento2/pull/21674)) + * [#22028](https://github.com/magento/magento2/issues/22028) -- Unable to update products via csv file, when products ids from file are from wide id range (fixed in [magento/magento2#22575](https://github.com/magento/magento2/pull/22575)) + * [#21558](https://github.com/magento/magento2/issues/21558) -- Navigation issue of review from product listing when click on review count (fixed in [magento/magento2#22794](https://github.com/magento/magento2/pull/22794)) + * [#22127](https://github.com/magento/magento2/issues/22127) -- Magento 2.3.0: getSize call on configurable collection leads to exception, if no product filters are applied (fixed in [magento/magento2#22186](https://github.com/magento/magento2/pull/22186)) + * [#22639](https://github.com/magento/magento2/issues/22639) -- Without select attribute click on add attribute it display all selected when add attribute again. (fixed in [magento/magento2#22724](https://github.com/magento/magento2/pull/22724)) + * [#22676](https://github.com/magento/magento2/issues/22676) -- Compare Products counter, and My Wish List counter vertical not aligned (fixed in [magento/magento2#22742](https://github.com/magento/magento2/pull/22742)) + * [#6659](https://github.com/magento/magento2/issues/6659) -- Disabled payment methods show in Customer Dashboard (fixed in [magento/magento2#22850](https://github.com/magento/magento2/pull/22850)) + * [#4628](https://github.com/magento/magento2/issues/4628) -- .lib-font-face mixin - Fixed font formats (fixed in [magento/magento2#22854](https://github.com/magento/magento2/pull/22854)) + * [#3795](https://github.com/magento/magento2/issues/3795) -- Validation messages missing from datepicker form elements (fixed in [magento/magento2#21397](https://github.com/magento/magento2/pull/21397)) + * [#22786](https://github.com/magento/magento2/issues/22786) -- The validation for UPS configurations triggers even if UPS is disabled for checkout (fixed in [magento/magento2#22787](https://github.com/magento/magento2/pull/22787)) + * [#22822](https://github.com/magento/magento2/issues/22822) -- [Shipping] The contact us link isn't showing on order tracking page (fixed in [magento/magento2#22823](https://github.com/magento/magento2/pull/22823)) + * [#21852](https://github.com/magento/magento2/issues/21852) -- Random Error while waiting for package deployed (fixed in [magento/magento2#22607](https://github.com/magento/magento2/pull/22607)) + * [#22563](https://github.com/magento/magento2/issues/22563) -- Parallelised execution of static content deploy is broken on 2.3-develop (fixed in [magento/magento2#22607](https://github.com/magento/magento2/pull/22607)) + * [#22736](https://github.com/magento/magento2/issues/22736) -- Cursor position not in right side of search keyword in search box when click on search again (Mobile issue) (fixed in [magento/magento2#22795](https://github.com/magento/magento2/pull/22795)) + * [#22875](https://github.com/magento/magento2/issues/22875) -- Billing Agreements page title need to be improved (fixed in [magento/magento2#22876](https://github.com/magento/magento2/pull/22876)) + * [#21214](https://github.com/magento/magento2/issues/21214) -- Luma theme Apply Discount Code section design improvement (fixed in [magento/magento2#21215](https://github.com/magento/magento2/pull/21215)) + * [#22143](https://github.com/magento/magento2/issues/22143) -- Varnish health check failing due to presence of id_prefix in env.php (fixed in [magento/magento2#22307](https://github.com/magento/magento2/pull/22307)) + * [#22317](https://github.com/magento/magento2/issues/22317) -- CodeSniffer should not mark correctly aligned DocBlock elements as code style violation. (fixed in [magento/magento2#22444](https://github.com/magento/magento2/pull/22444)) + * [#22396](https://github.com/magento/magento2/issues/22396) -- config:set fails with JSON values (fixed in [magento/magento2#22513](https://github.com/magento/magento2/pull/22513)) + * [#22506](https://github.com/magento/magento2/issues/22506) -- Search suggestion panel overlapping on advance reporting button (fixed in [magento/magento2#22520](https://github.com/magento/magento2/pull/22520)) + * [#22869](https://github.com/magento/magento2/issues/22869) -- REST: Updating a customer without store_id sets the store_id to default (fixed in [magento/magento2#22893](https://github.com/magento/magento2/pull/22893)) + * [#22924](https://github.com/magento/magento2/issues/22924) -- Store view label not in the middle of panel (fixed in [magento/magento2#22926](https://github.com/magento/magento2/pull/22926)) + * [#20186](https://github.com/magento/magento2/issues/20186) -- phpcs error on rule classes - must be of the type integer (fixed in [magento/magento2#22947](https://github.com/magento/magento2/pull/22947)) + * [#574](https://github.com/magento/magento2/issues/574) -- Maximum function nesting level of '100' reached (fixed in [magento/graphql-ce#694](https://github.com/magento/graphql-ce/pull/694)) + * [#686](https://github.com/magento/magento2/issues/686) -- Product save validation errors in the admin don't hide the overlay (fixed in [magento/graphql-ce#695](https://github.com/magento/graphql-ce/pull/695)) + * [#22380](https://github.com/magento/magento2/issues/22380) -- Checkout totals order in specific store (fixed in [magento/magento2#22387](https://github.com/magento/magento2/pull/22387)) + * [#18183](https://github.com/magento/magento2/issues/18183) -- Magento 2.2.6 coupon codes don't work anymore (fixed in [magento/magento2#22718](https://github.com/magento/magento2/pull/22718)) + * [#22899](https://github.com/magento/magento2/issues/22899) -- Incorrect return type at getListByCustomerId in PaymentTokenManagementInterface (fixed in [magento/magento2#22914](https://github.com/magento/magento2/pull/22914)) + * [#22686](https://github.com/magento/magento2/issues/22686) -- Shipment Create via API salesShipmentRepositoryV1 throw Fatal error in Admin Order -> Shipment -> View (fixed in [magento/magento2#22687](https://github.com/magento/magento2/pull/22687)) + * [#22767](https://github.com/magento/magento2/issues/22767) -- Not clear logic for loading CMS Pages with setStoreId function (fixed in [magento/magento2#22772](https://github.com/magento/magento2/pull/22772)) + * [#20788](https://github.com/magento/magento2/issues/20788) -- Listing page no equal spacing in product in list view (fixed in [magento/magento2#22931](https://github.com/magento/magento2/pull/22931)) + * [#23030](https://github.com/magento/magento2/issues/23030) -- Magento2 Swatch change Image does not slide to first Image (fixed in [magento/magento2#23033](https://github.com/magento/magento2/pull/23033)) + * [#23034](https://github.com/magento/magento2/issues/23034) -- Wrong behaviour of validation scroll (fixed in [magento/magento2#23035](https://github.com/magento/magento2/pull/23035)) + * [#12696](https://github.com/magento/magento2/issues/12696) -- Integration tests create stub modules in app/code (fixed in [magento/magento2#18459](https://github.com/magento/magento2/pull/18459)) + * [#13266](https://github.com/magento/magento2/issues/13266) -- Topmenu 'last' class not being set if the a parent is inactive (fixed in [magento/magento2#22071](https://github.com/magento/magento2/pull/22071)) + * [#22882](https://github.com/magento/magento2/issues/22882) -- Static content deploy - Don't shows error message, just stack trace (fixed in [magento/magento2#22884](https://github.com/magento/magento2/pull/22884)) + * [#23045](https://github.com/magento/magento2/issues/23045) -- Exceptions from data patches do not show root cause (fixed in [magento/magento2#23046](https://github.com/magento/magento2/pull/23046)) + * [#16446](https://github.com/magento/magento2/issues/16446) -- magento 2.2.2 text swatch switches product image even if attribute feature is disabled (fixed in [magento/magento2#19184](https://github.com/magento/magento2/pull/19184)) + * [#14492](https://github.com/magento/magento2/issues/14492) -- Creating Customer without password is directly confirmed (fixed in [magento/magento2#21394](https://github.com/magento/magento2/pull/21394)) + * [#21671](https://github.com/magento/magento2/issues/21671) -- Database Media Storage - Transaction emails logo not used when pub/media cleared (fixed in [magento/magento2#21674](https://github.com/magento/magento2/pull/21674)) + * [#22425](https://github.com/magento/magento2/issues/22425) -- wrong url redirect when edit product review from Customer view page (fixed in [magento/magento2#22426](https://github.com/magento/magento2/pull/22426)) + * [#22511](https://github.com/magento/magento2/issues/22511) -- Special From Date set to today's date when Use Default Date checked in Store scope (fixed in [magento/magento2#22521](https://github.com/magento/magento2/pull/22521)) + * [#23080](https://github.com/magento/magento2/issues/23080) -- Missing whitespace in mobile navigation for non-English websites (fixed in [magento/magento2#23081](https://github.com/magento/magento2/pull/23081)) + * [#19872](https://github.com/magento/magento2/issues/19872) -- Magento 2.3 category edit page "Select from gallery" button not working. (fixed in [magento/magento2#21131](https://github.com/magento/magento2/pull/21131)) + * [#22092](https://github.com/magento/magento2/issues/22092) -- Assigning Catalog Image from Gallery, then Saving Twice, Clears Image (fixed in [magento/magento2#21131](https://github.com/magento/magento2/pull/21131)) + * [#22087](https://github.com/magento/magento2/issues/22087) -- Products Ordered Report - Not grouped by product (fixed in [magento/magento2#22646](https://github.com/magento/magento2/pull/22646)) + * [#21546](https://github.com/magento/magento2/issues/21546) -- [2.3] Database Media Storage - New Product Images fail to be processed correctly (fixed in [magento/magento2#21605](https://github.com/magento/magento2/pull/21605)) + * [#21604](https://github.com/magento/magento2/issues/21604) -- Database Media Storage - Admin Product Edit page does not handle product images correctly in database storage mode (fixed in [magento/magento2#21605](https://github.com/magento/magento2/pull/21605)) + * [#4247](https://github.com/magento/magento2/issues/4247) -- getProductUrl does not allow to override the scope in backend context (fixed in [magento/magento2#21876](https://github.com/magento/magento2/pull/21876)) + * [#22940](https://github.com/magento/magento2/issues/22940) -- Reset feature does not clear the date (fixed in [magento/magento2#23007](https://github.com/magento/magento2/pull/23007)) + * [#23053](https://github.com/magento/magento2/issues/23053) -- Sendfriend works for products with visibility not visible individually (fixed in [magento/magento2#23118](https://github.com/magento/magento2/pull/23118)) + * [#675](https://github.com/magento/magento2/issues/675) -- Textarea element cols and rows (fixed in [magento/graphql-ce#677](https://github.com/magento/graphql-ce/pull/677)) + * [#682](https://github.com/magento/magento2/issues/682) -- \Magento\Framework\Pricing\PriceCurrencyInterface depends on Magento application code (fixed in [magento/graphql-ce#700](https://github.com/magento/graphql-ce/pull/700)) + * [#681](https://github.com/magento/magento2/issues/681) -- Magento\Framework\Xml\Parser class issues (fixed in [magento/graphql-ce#711](https://github.com/magento/graphql-ce/pull/711)) + * [#22484](https://github.com/magento/magento2/issues/22484) -- Customer address States are duplicated in backend (fixed in [magento/magento2#22637](https://github.com/magento/magento2/pull/22637)) + * [#23138](https://github.com/magento/magento2/issues/23138) -- Magento_Theme. Incorrect configuration file location (fixed in [magento/magento2#23140](https://github.com/magento/magento2/pull/23140)) + * [#22004](https://github.com/magento/magento2/issues/22004) -- ce231 - can't update attribute for all product (fixed in [magento/magento2#22704](https://github.com/magento/magento2/pull/22704)) + * [#22870](https://github.com/magento/magento2/issues/22870) -- ProductRepository fails to update an existing product with a changed SKU (fixed in [magento/magento2#22933](https://github.com/magento/magento2/pull/22933)) + * [#22808](https://github.com/magento/magento2/issues/22808) -- php bin/magento catalog:image:resize error if image is missing (fixed in [magento/magento2#23005](https://github.com/magento/magento2/pull/23005)) + * [#674](https://github.com/magento/magento2/issues/674) -- Widgets in content pages. (fixed in [magento/graphql-ce#709](https://github.com/magento/graphql-ce/pull/709)) + * [#683](https://github.com/magento/magento2/issues/683) -- CMS Router not routing correctly (fixed in [magento/graphql-ce#717](https://github.com/magento/graphql-ce/pull/717)) + * [#9113](https://github.com/magento/magento2/issues/9113) -- [Bug or Feature?] url_path attribute value is not populated for any product (fixed in [magento/graphql-ce#721](https://github.com/magento/graphql-ce/pull/721)) + * [#18337](https://github.com/magento/magento2/issues/18337) -- #search input is missing required attribute aria-expanded. (fixed in [magento/magento2#22942](https://github.com/magento/magento2/pull/22942)) + * [#23213](https://github.com/magento/magento2/issues/23213) -- Static content deploy showing percentage(%) two times in progress bar (fixed in [magento/magento2#23216](https://github.com/magento/magento2/pull/23216)) + * [#23238](https://github.com/magento/magento2/issues/23238) -- Apply coupon button act like remove coupon while create new order from admin (fixed in [magento/magento2#23250](https://github.com/magento/magento2/pull/23250)) + * [#4788](https://github.com/magento/magento2/issues/4788) -- Wrong sitemap product url (fixed in [magento/magento2#23129](https://github.com/magento/magento2/pull/23129)) + * [#22934](https://github.com/magento/magento2/issues/22934) -- Incorrect work of "Use Categories Path for Product URLs" in sitemap generation. (fixed in [magento/magento2#23129](https://github.com/magento/magento2/pull/23129)) + * [#23266](https://github.com/magento/magento2/issues/23266) -- Cannot filter admin user by ID (fixed in [magento/magento2#23267](https://github.com/magento/magento2/pull/23267)) + * [#23285](https://github.com/magento/magento2/issues/23285) -- Credit memo submit button(refund) stays disable after validation fails & unable to enable button (fixed in [magento/magento2#23286](https://github.com/magento/magento2/pull/23286)) + * [#486](https://github.com/magento/magento2/issues/486) -- Take inspiration from other frameworks (fixed in [magento/graphql-ce#714](https://github.com/magento/graphql-ce/pull/714)) + * [#716](https://github.com/magento/magento2/issues/716) -- Wrong mimetype returned by getMimeType from Magento library (fixed in [magento/graphql-ce#723](https://github.com/magento/graphql-ce/pull/723)) + * [#687](https://github.com/magento/magento2/issues/687) -- Improvement Idea: /var/cache/mage--X (fixed in [magento/graphql-ce#749](https://github.com/magento/graphql-ce/pull/749)) + * [#20038](https://github.com/magento/magento2/issues/20038) -- loading icon disappearing before background process completes for braintree payment (Admin order) (fixed in [magento/magento2#22675](https://github.com/magento/magento2/pull/22675)) + * [#23074](https://github.com/magento/magento2/issues/23074) -- Magento 2.3.1 - URL rewrite rules are not creating for product after update url key (fixed in [magento/magento2#23309](https://github.com/magento/magento2/pull/23309)) + * [#622](https://github.com/magento/magento2/issues/622) -- FIX Magento Search Please! (fixed in [magento/graphql-ce#626](https://github.com/magento/graphql-ce/pull/626)) + * [#732](https://github.com/magento/magento2/issues/732) -- Inconsistency between Select and Multiselect form elements. (fixed in [magento/graphql-ce#734](https://github.com/magento/graphql-ce/pull/734)) + * [#13227](https://github.com/magento/magento2/issues/13227) -- Knockout Recently Viewed contains wrong product url (with category path), also not correct url on product view page (fixed in [magento/magento2#22650](https://github.com/magento/magento2/pull/22650)) + * [#22638](https://github.com/magento/magento2/issues/22638) -- Asterisk(*) sign position does not consistent in admin (fixed in [magento/magento2#22800](https://github.com/magento/magento2/pull/22800)) + * [#22266](https://github.com/magento/magento2/issues/22266) -- Product Alert after login shows 404 page (fixed in [magento/magento2#23218](https://github.com/magento/magento2/pull/23218)) + * [#23230](https://github.com/magento/magento2/issues/23230) -- Sticky header floating under top when there is no buttons in the toolbar (fixed in [magento/magento2#23247](https://github.com/magento/magento2/pull/23247)) + * [#23333](https://github.com/magento/magento2/issues/23333) -- Incorrect payment method translation in order emails (fixed in [magento/magento2#23338](https://github.com/magento/magento2/pull/23338)) + * [#23346](https://github.com/magento/magento2/issues/23346) -- 'Test Connection' button is over-spanned (fixed in [magento/magento2#23367](https://github.com/magento/magento2/pull/23367)) + * [#21380](https://github.com/magento/magento2/issues/21380) -- Cron schedule is being duplicated (fixed in [magento/magento2#23312](https://github.com/magento/magento2/pull/23312)) + * [#21136](https://github.com/magento/magento2/issues/21136) -- Magento installation via metapackage: checkExtensions fails (fixed in [magento/magento2#22116](https://github.com/magento/magento2/pull/22116)) + * [#23233](https://github.com/magento/magento2/issues/23233) -- Alert widget doesn't trigger always method on showing the message (fixed in [magento/magento2#23234](https://github.com/magento/magento2/pull/23234)) + * [#21974](https://github.com/magento/magento2/issues/21974) -- Changes for PayPal affect core config fields with tooltip (fixed in [magento/magento2#23393](https://github.com/magento/magento2/pull/23393)) + * [#23377](https://github.com/magento/magento2/issues/23377) -- Mini cart loader not working first time magento2 (fixed in [magento/magento2#23394](https://github.com/magento/magento2/pull/23394)) + * [#22998](https://github.com/magento/magento2/issues/22998) -- POST on /orders fails when properties in the body are out of sequence (fixed in [magento/magento2#23048](https://github.com/magento/magento2/pull/23048)) + * [#23522](https://github.com/magento/magento2/issues/23522) -- UPS shipping booking and label generation gives error when shipper's street given more than 35 chars (fixed in [magento/magento2#23523](https://github.com/magento/magento2/pull/23523)) + * [#8298](https://github.com/magento/magento2/issues/8298) -- Mobile Menu Behavior at Incorrect Breakpoint (fixed in [magento/magento2#23528](https://github.com/magento/magento2/pull/23528)) + * [#22103](https://github.com/magento/magento2/issues/22103) -- Character Encoding in Plain Text Emails Fails since 2.2.8/2.3.0 due to emails no longer being sent as MIME (fixed in [magento/magento2#23535](https://github.com/magento/magento2/pull/23535)) + * [#23199](https://github.com/magento/magento2/issues/23199) -- NO sender in email header for magento 2 sales order and password change emails to customer (fixed in [magento/magento2#23535](https://github.com/magento/magento2/pull/23535)) + * [#23538](https://github.com/magento/magento2/issues/23538) -- wrong validation happen for max-words validation class (fixed in [magento/magento2#23541](https://github.com/magento/magento2/pull/23541)) + * [#21126](https://github.com/magento/magento2/issues/21126) -- Backend Import behavior design break (fixed in [magento/magento2#21128](https://github.com/magento/magento2/pull/21128)) + * [#23471](https://github.com/magento/magento2/issues/23471) -- Tooltip missing at store view lable in Cms page and Cms block (fixed in [magento/magento2#23474](https://github.com/magento/magento2/pull/23474)) + * [#23466](https://github.com/magento/magento2/issues/23466) -- Cart empty after update qty with -1 and change address. (fixed in [magento/magento2#23477](https://github.com/magento/magento2/pull/23477)) + * [#23467](https://github.com/magento/magento2/issues/23467) -- Phone and Zip not update if customer have no saved address (fixed in [magento/magento2#23494](https://github.com/magento/magento2/pull/23494)) + * [#23222](https://github.com/magento/magento2/issues/23222) -- setup:upgrade should return failure when app:config:import failed (fixed in [magento/magento2#23310](https://github.com/magento/magento2/pull/23310)) + * [#23354](https://github.com/magento/magento2/issues/23354) -- Data saving problem error showing when leave blank qty and update it (fixed in [magento/magento2#23360](https://github.com/magento/magento2/pull/23360)) + * [#23424](https://github.com/magento/magento2/issues/23424) -- Search by keyword didn't work properly with "0" value (fixed in [magento/magento2#23427](https://github.com/magento/magento2/pull/23427)) + * [#16234](https://github.com/magento/magento2/issues/16234) -- Unable to enter `+` character in widget content (fixed in [magento/magento2#23496](https://github.com/magento/magento2/pull/23496)) + * [#9798](https://github.com/magento/magento2/issues/9798) -- Problem adding attribute options to configurable product via REST Api (fixed in [magento/magento2#23529](https://github.com/magento/magento2/pull/23529)) + * [#6287](https://github.com/magento/magento2/issues/6287) -- Customer Admin Shopping Cart View Missing (fixed in [magento/magento2#20918](https://github.com/magento/magento2/pull/20918)) + * [#22545](https://github.com/magento/magento2/issues/22545) -- Status downloadable product stays pending after succesfull payment (fixed in [magento/magento2#22658](https://github.com/magento/magento2/pull/22658)) + * [#23383](https://github.com/magento/magento2/issues/23383) -- Products which are not assigned to any store are automatically being force-assigned a store ID after being saved (fixed in [magento/magento2#23500](https://github.com/magento/magento2/pull/23500)) + * [#22950](https://github.com/magento/magento2/issues/22950) -- Spacing issue for Gift message section in my account (fixed in [magento/magento2#23226](https://github.com/magento/magento2/pull/23226)) + * [#23606](https://github.com/magento/magento2/issues/23606) -- Default value for report filters might result in errors (fixed in [magento/magento2#23607](https://github.com/magento/magento2/pull/23607)) + * [#736](https://github.com/magento/magento2/issues/736) -- Access to Zend Framework classes (fixed in [magento/graphql-ce#747](https://github.com/magento/graphql-ce/pull/747)) + * [#739](https://github.com/magento/magento2/issues/739) -- Command line install script no longer exists. (fixed in [magento/graphql-ce#753](https://github.com/magento/graphql-ce/pull/753)) + * [#23435](https://github.com/magento/magento2/issues/23435) -- Catalog Products Filter in 2.3.2 (fixed in [magento/magento2#23444](https://github.com/magento/magento2/pull/23444)) + * [#12817](https://github.com/magento/magento2/issues/12817) -- Coupon code with canceled order (fixed in [magento/magento2#20579](https://github.com/magento/magento2/pull/20579)) + * [#23386](https://github.com/magento/magento2/issues/23386) -- Copy Service does not works properly for Entities which extends Data Object and implements ExtensibleDataInterface (fixed in [magento/magento2#23387](https://github.com/magento/magento2/pull/23387)) + * [#23345](https://github.com/magento/magento2/issues/23345) -- Creditmemo getOrder() method loads order incorrectly (fixed in [magento/magento2#23358](https://github.com/magento/magento2/pull/23358)) + * [#22814](https://github.com/magento/magento2/issues/22814) -- Product stock alert - unsubscribe not working (fixed in [magento/magento2#23459](https://github.com/magento/magento2/pull/23459)) + * [#23594](https://github.com/magento/magento2/issues/23594) -- Database Media Storage : php bin/magento catalog:images:resize fails when image does not exist locally (fixed in [magento/magento2#23598](https://github.com/magento/magento2/pull/23598)) + * [#23595](https://github.com/magento/magento2/issues/23595) -- Database Media Storage : php bin/magento catalog:images:resize fails to generate cached images in database (fixed in [magento/magento2#23598](https://github.com/magento/magento2/pull/23598)) + * [#23596](https://github.com/magento/magento2/issues/23596) -- Database Media Storage : Add new product image, cached images not generated (fixed in [magento/magento2#23598](https://github.com/magento/magento2/pull/23598)) + * [#23643](https://github.com/magento/magento2/issues/23643) -- Mime parts of email are no more encoded with quoted printable (fixed in [magento/magento2#23649](https://github.com/magento/magento2/pull/23649)) + * [#23597](https://github.com/magento/magento2/issues/23597) -- Database Media Storage : Difficulty changing mode to database media storage due to poor "Use Default Value" checkbox behaviour (fixed in [magento/magento2#23710](https://github.com/magento/magento2/pull/23710)) + * [#23510](https://github.com/magento/magento2/issues/23510) -- Product customizable options of Area type render issue in Dashboard (fixed in [magento/magento2#23524](https://github.com/magento/magento2/pull/23524)) + * [#22890](https://github.com/magento/magento2/issues/22890) -- Disabled config can be overwritten via admin (fixed in [magento/magento2#22891](https://github.com/magento/magento2/pull/22891)) + * [#23054](https://github.com/magento/magento2/issues/23054) -- Cron job not running after crashed once (fixed in [magento/magento2#23125](https://github.com/magento/magento2/pull/23125)) + * [#23135](https://github.com/magento/magento2/issues/23135) -- Insert Variable popup missing template variables for new templates (fixed in [magento/magento2#23173](https://github.com/magento/magento2/pull/23173)) + * [#23211](https://github.com/magento/magento2/issues/23211) -- Zero Subtotal Checkout erroneously says the default value for "Automatically Invoice All Items" is "Yes" (fixed in [magento/magento2#23688](https://github.com/magento/magento2/pull/23688)) + * [#23624](https://github.com/magento/magento2/issues/23624) -- [Authorize.net accept.js] "Place Order" button not being disabled (fixed in [magento/magento2#23718](https://github.com/magento/magento2/pull/23718)) + * [#23717](https://github.com/magento/magento2/issues/23717) -- dependency injection fails for \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface (fixed in [magento/magento2#23753](https://github.com/magento/magento2/pull/23753)) + * [#758](https://github.com/magento/magento2/issues/758) -- Coding standards: arrays (fixed in [magento/graphql-ce#759](https://github.com/magento/graphql-ce/pull/759)) + * [#14071](https://github.com/magento/magento2/issues/14071) -- Not able to change a position of last two related products in case of I've 20+ related products. (fixed in [magento/magento2#22984](https://github.com/magento/magento2/pull/22984)) + * [#22112](https://github.com/magento/magento2/issues/22112) -- Shipping address information is lost in billing step (fixed in [magento/magento2#23656](https://github.com/magento/magento2/pull/23656)) + * [#23654](https://github.com/magento/magento2/issues/23654) -- Frontend Label For Custom Order Status not Editable in Magento Admin in Single Store Mode (fixed in [magento/magento2#23681](https://github.com/magento/magento2/pull/23681)) + * [#23751](https://github.com/magento/magento2/issues/23751) -- Database Media Storage : PDF Logo file not database aware (fixed in [magento/magento2#23752](https://github.com/magento/magento2/pull/23752)) + * [#23678](https://github.com/magento/magento2/issues/23678) -- Can't see "Zero Subtotal Checkout" payment method settings if "Offline Payments" module is disabled (fixed in [magento/magento2#23679](https://github.com/magento/magento2/pull/23679)) + * [#23777](https://github.com/magento/magento2/issues/23777) -- "Discount Amount" field is validated after the page load without any action from user in Create New Catalog Rule form (fixed in [magento/magento2#23779](https://github.com/magento/magento2/pull/23779)) + * [#23789](https://github.com/magento/magento2/issues/23789) -- CommentLevelsSniff works incorrect with @magento_import statement. (fixed in [magento/magento2#23790](https://github.com/magento/magento2/pull/23790)) + * [#22702](https://github.com/magento/magento2/issues/22702) -- Toggle icon not working in create configuration Product creation Page (fixed in [magento/magento2#23803](https://github.com/magento/magento2/pull/23803)) + * [#167](https://github.com/magento/magento2/issues/167) -- Fatal error: Class 'Mage' not found (fixed in [magento/graphql-ce#351](https://github.com/magento/graphql-ce/pull/351)) + * [#438](https://github.com/magento/magento2/issues/438) -- [Feature request] Price slider (fixed in [magento/graphql-ce#699](https://github.com/magento/graphql-ce/pull/699)) + * [#702](https://github.com/magento/magento2/issues/702) -- Base table or view not found (fixed in [magento/graphql-ce#779](https://github.com/magento/graphql-ce/pull/779)) + * [#738](https://github.com/magento/magento2/issues/738) -- pub/setup missing in 0.1.0-alpha103 (fixed in [magento/graphql-ce#789](https://github.com/magento/graphql-ce/pull/789)) + * [#23405](https://github.com/magento/magento2/issues/23405) -- 2.3.2 installed and bin/magento setup:upgrade not working (fixed in [magento/magento2#23866](https://github.com/magento/magento2/pull/23866)) + * [#23900](https://github.com/magento/magento2/issues/23900) -- Report->Product->Downloads has wrong ACL (fixed in [magento/magento2#23901](https://github.com/magento/magento2/pull/23901)) + * [#23904](https://github.com/magento/magento2/issues/23904) -- No auto-focus after validation at "Create Configurations" button => User can not see the error message (fixed in [magento/magento2#23905](https://github.com/magento/magento2/pull/23905)) + * [#23916](https://github.com/magento/magento2/issues/23916) -- Missing Validation at some Payment Method Settings (fixed in [magento/magento2#23917](https://github.com/magento/magento2/pull/23917)) + * [#23932](https://github.com/magento/magento2/issues/23932) -- Decimal quantity is not displayed for wishlist items. (fixed in [magento/magento2#23933](https://github.com/magento/magento2/pull/23933)) +* GitHub pull requests: + * [magento/magento2#20135](https://github.com/magento/magento2/pull/20135) -- issue fixed #20124 Sort By label is hidden by Shop By Menu on listing (by @cedarvinda) + * [magento/magento2#22020](https://github.com/magento/magento2/pull/22020) -- Non existing file, when adding image to gallery with move option. Fix for #21978 (by @dudzio12) + * [magento/magento2#22260](https://github.com/magento/magento2/pull/22260) -- Disabling "Display on Product Details Page" the button is shown anyway. (by @Nazar65) + * [magento/magento2#22287](https://github.com/magento/magento2/pull/22287) -- #222249 configurable product images wrong sorting fix (by @Wirson) + * [magento/magento2#22526](https://github.com/magento/magento2/pull/22526) -- code cleanup (http to https) (by @ravi-chandra3197) + * [magento/magento2#22532](https://github.com/magento/magento2/pull/22532) -- fixed issue 22527 wishlist and compare icon alignment (by @sanjaychouhan-webkul) + * [magento/magento2#22423](https://github.com/magento/magento2/pull/22423) -- Store view specific labels cut in left navigation menu #22406 (by @sudhanshu-bajaj) + * [magento/magento2#22561](https://github.com/magento/magento2/pull/22561) -- [Catalog|Eav] Revert change of PR magento/magento2#13302 not included into revert commit (by @Den4ik) + * [magento/magento2#22569](https://github.com/magento/magento2/pull/22569) -- 2.3 develop pr1 (by @abhinay111222) + * [magento/magento2#22594](https://github.com/magento/magento2/pull/22594) -- Fixed typo issue (by @AfreenScarlet) + * [magento/magento2#22599](https://github.com/magento/magento2/pull/22599) -- Correct spelling (by @ravi-chandra3197) + * [magento/magento2#22621](https://github.com/magento/magento2/pull/22621) -- Resolved Typo (by @UdgamN) + * [magento/magento2#19767](https://github.com/magento/magento2/pull/19767) -- Prevent display of token when save for later is not selected (by @pmclain) + * [magento/magento2#21744](https://github.com/magento/magento2/pull/21744) -- Custom option type select - Allow modify list of single selection option types (by @ihor-sviziev) + * [magento/magento2#21992](https://github.com/magento/magento2/pull/21992) -- #21473: Form element validation is not triggered when validation rules... (by @kisroman) + * [magento/magento2#22493](https://github.com/magento/magento2/pull/22493) -- Update credit-card-number-validator.js (by @justin-at-bounteous) + * [magento/magento2#22643](https://github.com/magento/magento2/pull/22643) -- Fixed typo issue and added missing header in customer sales order grid (by @vishal-7037) + * [magento/magento2#22656](https://github.com/magento/magento2/pull/22656) -- issues #22647 fixed, In customer account create page word not readable, should use '-' after break to new line In mobile view (by @cedarvinda) + * [magento/magento2#22720](https://github.com/magento/magento2/pull/22720) -- Fixed:#22395 (by @satyaprakashpatel) + * [magento/magento2#22558](https://github.com/magento/magento2/pull/22558) -- Additional condition in getRegion() method (by @Leone) + * [magento/magento2#22560](https://github.com/magento/magento2/pull/22560) -- Fix undefined methods 'addClass' and `removeClass` on a PrototypeJS Element (by @markvds) + * [magento/magento2#22606](https://github.com/magento/magento2/pull/22606) -- Fix Exception While Creating an Order in the Admin (by @justin-at-bounteous) + * [magento/magento2#22628](https://github.com/magento/magento2/pull/22628) -- Add a missting colon in the pdf page. (by @Hailong) + * [magento/magento2#22644](https://github.com/magento/magento2/pull/22644) -- Issue fixed #22636 arrow toggle not changing only showing to down It should be toggle as every where is working (by @cedarvinda) + * [magento/magento2#22664](https://github.com/magento/magento2/pull/22664) -- Fixed Typo Error (by @LuciferStrome) + * [magento/magento2#22729](https://github.com/magento/magento2/pull/22729) -- Fixed Typo Issue (by @jitendra-cedcoss) + * [magento/magento2#22734](https://github.com/magento/magento2/pull/22734) -- Ignores allure-results in git. (by @hostep) + * [magento/magento2#22758](https://github.com/magento/magento2/pull/22758) -- Correct spelling (by @ravi-chandra3197) + * [magento/magento2#22798](https://github.com/magento/magento2/pull/22798) -- Disable Travis builds - 2.3-develop (by @okorshenko) + * [magento/magento2#22126](https://github.com/magento/magento2/pull/22126) -- Remove unnecessary form on order success page (by @danielgoodwin97) + * [magento/magento2#22655](https://github.com/magento/magento2/pull/22655) -- Fixed Issue #22640 (by @Surabhi-Cedcoss) + * [magento/magento2#22657](https://github.com/magento/magento2/pull/22657) -- 404 not found form validation url when updating quantity in cart page (by @gulshanchitransh) + * [magento/magento2#22739](https://github.com/magento/magento2/pull/22739) -- Revert "Magento backend catalog cost without currency symbol" as Cost... (by @orlangur) + * [magento/magento2#22779](https://github.com/magento/magento2/pull/22779) -- #22771 Remove hardcoded height for admin textarea (by @serhiyzhovnir) + * [magento/magento2#22791](https://github.com/magento/magento2/pull/22791) -- Fixed issue #22788 (by @gauravagarwal1001) + * [magento/magento2#19584](https://github.com/magento/magento2/pull/19584) -- Tierprice can t save float percentage value 18651 (by @novikor) + * [magento/magento2#21675](https://github.com/magento/magento2/pull/21675) -- [2.3] Database Media Storage - Design Config Save functions to be Database Media Storage aware (by @gwharton) + * [magento/magento2#21917](https://github.com/magento/magento2/pull/21917) -- Prevent duplicate variation error during import of configurable products with numerical SKUs (by @alexander-aleman) + * [magento/magento2#22463](https://github.com/magento/magento2/pull/22463) -- Set timezone on DateTime object not in constructor (by @NathMorgan) + * [magento/magento2#22575](https://github.com/magento/magento2/pull/22575) -- Fix for update products via csv file (fix for 22028) (by @mtwegrzycki) + * [magento/magento2#22794](https://github.com/magento/magento2/pull/22794) -- fixed issue #21558 - Navigation issue of review from product listing (by @sanjaychouhan-webkul) + * [magento/magento2#22844](https://github.com/magento/magento2/pull/22844) -- Allow to specify a field to be checked in the response (by @diazwatson) + * [magento/magento2#22186](https://github.com/magento/magento2/pull/22186) -- 22127: check that products is set (by @davidverholen) + * [magento/magento2#22418](https://github.com/magento/magento2/pull/22418) -- Patch the prototype pollution vulnerability in jQuery < 3.4.0 (CVE-2019-11358) (by @DanielRuf) + * [magento/magento2#22724](https://github.com/magento/magento2/pull/22724) -- Fixed issue #22639: Without select attribute click on add attribute it display all selected when add attribute again. (by @maheshWebkul721) + * [magento/magento2#22742](https://github.com/magento/magento2/pull/22742) -- issue #22676 fixed - Compare Products counter, and My Wish List count... (by @sanjaychouhan-webkul) + * [magento/magento2#22850](https://github.com/magento/magento2/pull/22850) -- only show customer account sections if payment method is active (by @torhoehn) + * [magento/magento2#22854](https://github.com/magento/magento2/pull/22854) -- fix #4628 font-face mixin add fromat option (by @Karlasa) + * [magento/magento2#22868](https://github.com/magento/magento2/pull/22868) -- fix date calculation for report's years interval (by @polkan-msk) + * [magento/magento2#21397](https://github.com/magento/magento2/pull/21397) -- Fixed Validation messages missing from datepicker form elements (by @ravi-chandra3197) + * [magento/magento2#22694](https://github.com/magento/magento2/pull/22694) -- Fixed Issue #20234 (by @surbhi-ranosys) + * [magento/magento2#22787](https://github.com/magento/magento2/pull/22787) -- #22786 Add dependency for UPS required fields to avoid validation for these fields if UPS Shipping is not active (by @serhiyzhovnir) + * [magento/magento2#22823](https://github.com/magento/magento2/pull/22823) -- [Shipping] Adjusting the Contact Us Xpath (by @eduard13) + * [magento/magento2#22830](https://github.com/magento/magento2/pull/22830) -- Removing "if" block and making code more legible (by @matheusgontijo) + * [magento/magento2#22839](https://github.com/magento/magento2/pull/22839) -- FIX: Add missing Stories and Severity to Test cases (by @lbajsarowicz) + * [magento/magento2#22858](https://github.com/magento/magento2/pull/22858) -- Grammatical mistake in the comments (by @sudhanshu-bajaj) + * [magento/magento2#22889](https://github.com/magento/magento2/pull/22889) -- Replace hardcoded CarierCode from createShippingMethod() (by @wigman) + * [magento/magento2#22922](https://github.com/magento/magento2/pull/22922) -- [BUGFIX] Set correct cron instance for catalog_product_frontend_actio... (by @lewisvoncken) + * [magento/magento2#22607](https://github.com/magento/magento2/pull/22607) -- Implement Better Error Handling and Fix Waits on Null PIDs in Parallel SCD Execution (by @davidalger) + * [magento/magento2#22795](https://github.com/magento/magento2/pull/22795) -- fixed issue #22736 - Cursor position not in right side of search keyword in mobile (by @sanjaychouhan-webkul) + * [magento/magento2#22876](https://github.com/magento/magento2/pull/22876) -- Fixed issue #22875 Billing Agreements page title need to be improved (by @amitvishvakarma) + * [magento/magento2#21215](https://github.com/magento/magento2/pull/21215) -- fixed-Discount-Code-improvement-21214 (by @abrarpathan19) + * [magento/magento2#22307](https://github.com/magento/magento2/pull/22307) -- Varnish health check failing due to presence of id_prefix in env.php (by @Nazar65) + * [magento/magento2#22444](https://github.com/magento/magento2/pull/22444) -- magento/magento2#22317: PR#22321 fix. (by @p-bystritsky) + * [magento/magento2#22513](https://github.com/magento/magento2/pull/22513) -- Fixed #22396 config:set fails with JSON values (by @shikhamis11) + * [magento/magento2#22520](https://github.com/magento/magento2/pull/22520) -- Fixed #22506: Search suggestion panel overlapping on advance reporting button (by @webkul-ajaysaini) + * [magento/magento2#22760](https://github.com/magento/magento2/pull/22760) -- [Forwardport] Magento Catalog - fix custom option type text price conversion for mu... (by @ihor-sviziev) + * [magento/magento2#22893](https://github.com/magento/magento2/pull/22893) -- #22869 - defaulting customer storeId fix (by @Wirson) + * [magento/magento2#22926](https://github.com/magento/magento2/pull/22926) -- Store view label not in the middle of panel (by @speedy008) + * [magento/magento2#22947](https://github.com/magento/magento2/pull/22947) -- phpcs error on rule classes - must be of the type integer (by @Nazar65) + * [magento/magento2#22951](https://github.com/magento/magento2/pull/22951) -- Update the contributing.md to match the new beginners guide (by @dmanners) + * [magento/magento2#22387](https://github.com/magento/magento2/pull/22387) -- Checkout totals order in specific store (by @krnshah) + * [magento/magento2#22718](https://github.com/magento/magento2/pull/22718) -- Resolved issue coupon codes don't work anymore #18183 (by @this-adarsh) + * [magento/magento2#22914](https://github.com/magento/magento2/pull/22914) -- #22899 Fix the issue with Incorrect return type at getListByCustomerId in PaymentTokenManagementInterface (by @serhiyzhovnir) + * [magento/magento2#22687](https://github.com/magento/magento2/pull/22687) -- #22686 Shipment view fixed for Fatal error. (by @milindsingh) + * [magento/magento2#22772](https://github.com/magento/magento2/pull/22772) -- Fixed issue #22767: Not clear logic for loading CMS Pages with setStoreId function (by @maheshWebkul721) + * [magento/magento2#22931](https://github.com/magento/magento2/pull/22931) -- [Fixed-20788: Listing page no equal spacing in product in list view] (by @hitesh-wagento) + * [magento/magento2#22965](https://github.com/magento/magento2/pull/22965) -- Simplify if else catalog search full text data provider (by @sankalpshekhar) + * [magento/magento2#23011](https://github.com/magento/magento2/pull/23011) -- Fix typehint (by @amenk) + * [magento/magento2#22920](https://github.com/magento/magento2/pull/22920) -- Mview Indexers getList should return integer values of id's and not strings (by @mhodge13) + * [magento/magento2#23020](https://github.com/magento/magento2/pull/23020) -- Remove direct use of object manager (by @AnshuMishra17) + * [magento/magento2#23033](https://github.com/magento/magento2/pull/23033) -- Issue fix #23030: Swatch change Image does not slide to first Image (by @milindsingh) + * [magento/magento2#23035](https://github.com/magento/magento2/pull/23035) -- [Validator] Fix wrong behaviour of validation scroll (by @Den4ik) + * [magento/magento2#18459](https://github.com/magento/magento2/pull/18459) -- 12696 Delete all test modules after integration tests (by @avstudnitz) + * [magento/magento2#19897](https://github.com/magento/magento2/pull/19897) -- Re-enable PriceBox block caching (by @brucemead) + * [magento/magento2#21200](https://github.com/magento/magento2/pull/21200) -- [FEATURE] Don't load product collection in review observer (by @Den4ik) + * [magento/magento2#22071](https://github.com/magento/magento2/pull/22071) -- Make sure 'last' class is set on top menu (by @arnoudhgz) + * [magento/magento2#22821](https://github.com/magento/magento2/pull/22821) -- Customer Account Forgot Password page title fix (by @textarea) + * [magento/magento2#22884](https://github.com/magento/magento2/pull/22884) -- Show exception message during SCD failure (by @ihor-sviziev) + * [magento/magento2#22989](https://github.com/magento/magento2/pull/22989) -- Properly transliterate German Umlauts (by @amenk) + * [magento/magento2#23036](https://github.com/magento/magento2/pull/23036) -- [Framework] Reassign fields variable after converting to array (by @Den4ik) + * [magento/magento2#23040](https://github.com/magento/magento2/pull/23040) -- Don't create a new account-nav block - use existing instead. (by @vovayatsyuk) + * [magento/magento2#23046](https://github.com/magento/magento2/pull/23046) -- Add more descriptive exception when data patch fails to apply. (by @ashsmith) + * [magento/magento2#23067](https://github.com/magento/magento2/pull/23067) -- Create Security.md file to show on GitHub Security/Policy page (by @piotrekkaminski) + * [magento/magento2#14384](https://github.com/magento/magento2/pull/14384) -- Don't throw shipping method exception when creating quote with only virtual products in API (by @Maikel-Koek) + * [magento/magento2#19184](https://github.com/magento/magento2/pull/19184) -- Fixed magento text swatch switches product image even if attribute feature is disabled #16446 (by @ravi-chandra3197) + * [magento/magento2#21394](https://github.com/magento/magento2/pull/21394) -- [2.3]creating customer without password is directly confirmed 14492 (by @novikor) + * [magento/magento2#21674](https://github.com/magento/magento2/pull/21674) -- [2.3] Database Media Storage - Transactional Emails will now extract image from database in Database Media Storage mode (by @gwharton) + * [magento/magento2#22336](https://github.com/magento/magento2/pull/22336) -- fix clean_cache plugin flush mode (by @thomas-kl1) + * [magento/magento2#22426](https://github.com/magento/magento2/pull/22426) -- Fixed wrong url redirect when edit product review from Customer view page (by @ravi-chandra3197) + * [magento/magento2#22521](https://github.com/magento/magento2/pull/22521) -- Fixed 22511 (by @maheshWebkul721) + * [magento/magento2#22626](https://github.com/magento/magento2/pull/22626) -- resolved typo error (by @nehaguptacedcoss) + * [magento/magento2#22834](https://github.com/magento/magento2/pull/22834) -- #16445 - getRegionHtmlSelect does not have configuration - resolved (by @nikunjskd20) + * [magento/magento2#22937](https://github.com/magento/magento2/pull/22937) -- Mark Elasticsearch 6 support for synonyms (by @aapokiiso) + * [magento/magento2#23081](https://github.com/magento/magento2/pull/23081) -- Fix missing whitespace in mobile navigation for non-English websites (by @alexeya-ven) + * [magento/magento2#21131](https://github.com/magento/magento2/pull/21131) -- Fix Issue #19872 - checking if image is in media directory (by @Bartlomiejsz) + * [magento/magento2#22341](https://github.com/magento/magento2/pull/22341) -- Apply coupoun and scroll top to check. applied successfully or not (by @krnshah) + * [magento/magento2#22646](https://github.com/magento/magento2/pull/22646) -- Fixed Issue #22087 (by @Surabhi-Cedcoss) + * [magento/magento2#23025](https://github.com/magento/magento2/pull/23025) -- Re-enable XML as request and response types within the SwaggerUI (by @sweikenb) + * [magento/magento2#20848](https://github.com/magento/magento2/pull/20848) -- Partial docs fixes in Newsletter module (by @SikailoISM) + * [magento/magento2#21605](https://github.com/magento/magento2/pull/21605) -- [2.3] Database Media Storage - Admin Product Edit Page handles recreates images correctly when pub/media/catalog is cleared. (by @gwharton) + * [magento/magento2#21876](https://github.com/magento/magento2/pull/21876) -- $product->getUrlInStore() does not allow to override the scope in backend context (by @Nazar65) + * [magento/magento2#23007](https://github.com/magento/magento2/pull/23007) -- [Fixed] Reset feature does not clear the date (by @niravkrish) + * [magento/magento2#23118](https://github.com/magento/magento2/pull/23118) -- #23053 : sendfriend verifies product visibility instead of status (by @Wirson) + * [magento/magento2#22637](https://github.com/magento/magento2/pull/22637) -- Fixed #22484 Customer address States are duplicated in backend (by @shikhamis11) + * [magento/magento2#23140](https://github.com/magento/magento2/pull/23140) -- magento/magento2#23138: Magento_Theme. Incorrect configuration file location (by @atwixfirster) + * [magento/magento2#23179](https://github.com/magento/magento2/pull/23179) -- Fix for translation function (by @kkdg) + * [magento/magento2#22704](https://github.com/magento/magento2/pull/22704) -- Fixed #22004 can't update attribute for all product (by @shikhamis11) + * [magento/magento2#22933](https://github.com/magento/magento2/pull/22933) -- magento/magento2#22870: ProductRepository fails to update an existing product with a changed SKU. (by @p-bystritsky) + * [magento/magento2#23005](https://github.com/magento/magento2/pull/23005) -- Improve command catalog:images:resize (by @tdgroot) + * [magento/magento2#22942](https://github.com/magento/magento2/pull/22942) -- Fixed issue #18337 (by @geet07) + * [magento/magento2#23216](https://github.com/magento/magento2/pull/23216) -- Fixed #23213 Static content deploy showing percentage symbol two times in progress bar (by @amitvishvakarma) + * [magento/magento2#23244](https://github.com/magento/magento2/pull/23244) -- Update CONTRIBUTING.md (by @diazwatson) + * [magento/magento2#23248](https://github.com/magento/magento2/pull/23248) -- fix tooltip toggle selector typo (by @Karlasa) + * [magento/magento2#23250](https://github.com/magento/magento2/pull/23250) -- Fixed #23238 Apply button act like remove button while create new order from admin (by @gauravagarwal1001) + * [magento/magento2#22211](https://github.com/magento/magento2/pull/22211) -- Show converted value for validateForRefund error message (by @kassner) + * [magento/magento2#23129](https://github.com/magento/magento2/pull/23129) -- #22934 Improved sitemap product generation logic (by @sergiy-v) + * [magento/magento2#23201](https://github.com/magento/magento2/pull/23201) -- MFTF: Use AdminLoginActionGroup for AdminLoginTest - easiest use of ActionGroup (by @lbajsarowicz) + * [magento/magento2#23100](https://github.com/magento/magento2/pull/23100) -- Resolve issue with improper EAV attribute join statement (by @udovicic) + * [magento/magento2#23267](https://github.com/magento/magento2/pull/23267) -- Add filter index for ID column in adminhtml user grid (by @JeroenVanLeusden) + * [magento/magento2#23286](https://github.com/magento/magento2/pull/23286) -- Fixed Credit memo submit button(refund) stays disable after validation fails & unable to enable button issue. (by @nishantjariwala) + * [magento/magento2#23292](https://github.com/magento/magento2/pull/23292) -- revert Properly transliterate German Umlauts (by @Nazar65) + * [magento/magento2#23307](https://github.com/magento/magento2/pull/23307) -- [Ui] Allow to define listing configuration via ui component xml (by @Den4ik) + * [magento/magento2#23335](https://github.com/magento/magento2/pull/23335) -- Correct spelling (by @ravi-chandra3197) + * [magento/magento2#22675](https://github.com/magento/magento2/pull/22675) -- Fixed #20038 loading icon disappearing before background process completes for Braintree payment in Admin (by @kunal-rtpl) + * [magento/magento2#23174](https://github.com/magento/magento2/pull/23174) -- Move Quote related Plugins to correct module (by @sankalpshekhar) + * [magento/magento2#23309](https://github.com/magento/magento2/pull/23309) -- magento/magento2#23074: update correct product URL rewrites after changing category url key (by @sta1r) + * [magento/magento2#23347](https://github.com/magento/magento2/pull/23347) -- Fixes incorrect file reference in a comment in a .htaccess file. (by @hostep) + * [magento/magento2#22650](https://github.com/magento/magento2/pull/22650) -- Fixes issue #13227 (by @atishgoswami) + * [magento/magento2#22800](https://github.com/magento/magento2/pull/22800) -- fixed issue #22638 - Asterisk(*) sign position does not consistent in admin (by @sanjaychouhan-webkul) + * [magento/magento2#22910](https://github.com/magento/magento2/pull/22910) -- Do an empty check instead of isset check on image removed (by @arnoudhgz) + * [magento/magento2#23218](https://github.com/magento/magento2/pull/23218) -- Fixed #22266: 404 message for product alerts when not logged in (by @ArjenMiedema) + * [magento/magento2#23247](https://github.com/magento/magento2/pull/23247) -- Fixed 23230 : Sticky header floating under top when there is no buttons in the toolbar (by @konarshankar07) + * [magento/magento2#23338](https://github.com/magento/magento2/pull/23338) -- Fix issue with incorrect payment translation in sales emails (by @alexeya-ven) + * [magento/magento2#23366](https://github.com/magento/magento2/pull/23366) -- Correct spelling (by @ravi-chandra3197) + * [magento/magento2#23367](https://github.com/magento/magento2/pull/23367) -- #23346: 'Test Connection' button is over-spanned (by @konarshankar07) + * [magento/magento2#22671](https://github.com/magento/magento2/pull/22671) -- Change exportButton option cvs (by @ajeetsinghcedcoss) + * [magento/magento2#23240](https://github.com/magento/magento2/pull/23240) -- Refactor: Improve mview code readability (by @lbajsarowicz) + * [magento/magento2#23280](https://github.com/magento/magento2/pull/23280) -- Ensure page is loaded after order click actions (by @fooman) + * [magento/magento2#23306](https://github.com/magento/magento2/pull/23306) -- FS/23038 Decimal qty with Increment is with specific values are not adding in cart (by @sertlab) + * [magento/magento2#23312](https://github.com/magento/magento2/pull/23312) -- Added function to check against running/pending/successful cron tasks (by @chickenland) + * [magento/magento2#22116](https://github.com/magento/magento2/pull/22116) -- Fix magento root package identification for metapackage installation (by @oleksii-lisovyi) + * [magento/magento2#23234](https://github.com/magento/magento2/pull/23234) -- [Ui] Calling the always action on opening and closing the modal. (by @eduard13) + * [magento/magento2#23353](https://github.com/magento/magento2/pull/23353) -- Get review entity id by code instead hard-coded. (by @DaniloEmpire) + * [magento/magento2#23393](https://github.com/magento/magento2/pull/23393) -- Fixed issue #21974 (by @geet07) + * [magento/magento2#23394](https://github.com/magento/magento2/pull/23394) -- Fixed issue #23377 (by @geet07) + * [magento/magento2#23403](https://github.com/magento/magento2/pull/23403) -- Remove rogue closing tag from store-switcher template (by @sta1r) + * [magento/magento2#22987](https://github.com/magento/magento2/pull/22987) -- Fixed apply discount coupons for bundle product (by @NikolasSumrak) + * [magento/magento2#23048](https://github.com/magento/magento2/pull/23048) -- #22998 : failing order creation with api when no address email is provided (by @Wirson) + * [magento/magento2#23390](https://github.com/magento/magento2/pull/23390) -- Changed logic so that _scrollToTopIfVisible is called only if element is in viewport. Previously it was called only when the element was outside it. (by @oskarolaussen) + * [magento/magento2#23425](https://github.com/magento/magento2/pull/23425) -- The best practices for SEO meta sequence. (by @vaseemishak) + * [magento/magento2#23523](https://github.com/magento/magento2/pull/23523) -- Issue #23522 UPS shipping booking and label generation gives error when shipper's street given more than 35 chars (by @ankurvr) + * [magento/magento2#23528](https://github.com/magento/magento2/pull/23528) -- move breakpoint by -1px to make nav work correctly at viewport 768 (by @bobemoe) + * [magento/magento2#23532](https://github.com/magento/magento2/pull/23532) -- Correct array type hints in Visibility model (by @pmclain) + * [magento/magento2#23535](https://github.com/magento/magento2/pull/23535) -- [2.3] Plain Text Emails are now sent with correct MIME Encoding (by @gwharton) + * [magento/magento2#23541](https://github.com/magento/magento2/pull/23541) -- fix validation class for max-words (by @sunilit42) + * [magento/magento2#21128](https://github.com/magento/magento2/pull/21128) -- Fix issue 21126 : Import design break issue resolved (by @speedy008) + * [magento/magento2#22213](https://github.com/magento/magento2/pull/22213) -- Date column ui component locale date format (by @Karlasa) + * [magento/magento2#23457](https://github.com/magento/magento2/pull/23457) -- Update CartTotalRepository.php (by @UlyanaKiklevich) + * [magento/magento2#23474](https://github.com/magento/magento2/pull/23474) -- Fixed tooltip missing at store view lable in Cms page and Cms block (by @dipeshrangani) + * [magento/magento2#23477](https://github.com/magento/magento2/pull/23477) -- Added quantity validation on Shipping Multiple Address Page (by @nirmalraval18) + * [magento/magento2#23494](https://github.com/magento/magento2/pull/23494) -- Removed editor from phone and zipcode (by @kazim-krish) + * [magento/magento2#23310](https://github.com/magento/magento2/pull/23310) -- magento/magento-2#23222: setup:upgrade should return failure when app... (by @ProcessEight) + * [magento/magento2#23360](https://github.com/magento/magento2/pull/23360) -- #23354 : Data saving problem error showing when leave blank qty and update it (by @konarshankar07) + * [magento/magento2#23427](https://github.com/magento/magento2/pull/23427) -- 23424: fixed search with 0 (by @jeysmook) + * [magento/magento2#23496](https://github.com/magento/magento2/pull/23496) -- Resolved + character issue in custom widget (by @sarfarazbheda) + * [magento/magento2#23529](https://github.com/magento/magento2/pull/23529) -- Feature/9798 updating configurable product options based on produc id and sku (by @lpouwelse) + * [magento/magento2#20918](https://github.com/magento/magento2/pull/20918) -- Enabled 'Shopping Cart' tab for customer edit interface in admin (by @rav-redchamps) + * [magento/magento2#22624](https://github.com/magento/magento2/pull/22624) -- Resolve Typo (by @prashantsharmacedcoss) + * [magento/magento2#22658](https://github.com/magento/magento2/pull/22658) -- Fixed #22545 Status downloadable product stays pending after succesfu... (by @shikhamis11) + * [magento/magento2#23500](https://github.com/magento/magento2/pull/23500) -- Fixed issue #23383 (by @manishgoswamij) + * [magento/magento2#23226](https://github.com/magento/magento2/pull/23226) -- Spacing issue for Gift message section in my account (by @amjadm61) + * [magento/magento2#23272](https://github.com/magento/magento2/pull/23272) -- hide or show the select for regions instead of enabling/disabling in customer registration (by @UB3RL33T) + * [magento/magento2#23593](https://github.com/magento/magento2/pull/23593) -- A small fix to improve format customer acl xml. (by @mrkhoa99) + * [magento/magento2#23607](https://github.com/magento/magento2/pull/23607) -- Default filter for reports set to past month (by @rogyar) + * [magento/magento2#22138](https://github.com/magento/magento2/pull/22138) -- BeanShells are changed to correct using of variables (by @AnnaShepa) + * [magento/magento2#22733](https://github.com/magento/magento2/pull/22733) -- Adds module:config:status command which checks if the module config i... (by @hostep) + * [magento/magento2#23351](https://github.com/magento/magento2/pull/23351) -- Fix some framework coding issues (by @fooman) + * [magento/magento2#23444](https://github.com/magento/magento2/pull/23444) -- Fix missing attribute_id condition from filter (by @mattijv) + * [magento/magento2#20579](https://github.com/magento/magento2/pull/20579) -- magento/magento2#12817: [Forwardport] Coupon code with canceled order. (by @p-bystritsky) + * [magento/magento2#23387](https://github.com/magento/magento2/pull/23387) -- magento/magento2#23386: Copy Service does not work properly for Entities which extends Data Object and implements ExtensibleDataInterface. (by @swnsma) + * [magento/magento2#23358](https://github.com/magento/magento2/pull/23358) -- magento/magento2#23345: Creditmemo getOrder() method loads order incorrectly. (by @p-bystritsky) + * [magento/magento2#23459](https://github.com/magento/magento2/pull/23459) -- magento/magento2#22814: Product stock alert - unsubscribe not working (by @yuriichayka) + * [magento/magento2#23598](https://github.com/magento/magento2/pull/23598) -- [2.3] magento catalog:images:resize now Database Media Storage mode aware (by @gwharton) + * [magento/magento2#23291](https://github.com/magento/magento2/pull/23291) -- Optimized dev:urn-catalog:generate for PHPStorm (by @JeroenBoersma) + * [magento/magento2#23592](https://github.com/magento/magento2/pull/23592) -- [Unit] Fix broken unit tests (by @Den4ik) + * [magento/magento2#23649](https://github.com/magento/magento2/pull/23649) -- [2.3] Transfer Encoding of emails changed to QUOTED-PRINTABLE (by @gwharton) + * [magento/magento2#23652](https://github.com/magento/magento2/pull/23652) -- Add missing getClass() to image.phtml so it is more like image_with_borders.phtml (by @woutersamaey) + * [magento/magento2#23710](https://github.com/magento/magento2/pull/23710) -- [2.3] Improve Database Media Storage Configuration settings usability (by @gwharton) + * [magento/magento2#23735](https://github.com/magento/magento2/pull/23735) -- Fixed typo in deploy module README.md (by @arnolds) + * [magento/magento2#22717](https://github.com/magento/magento2/pull/22717) -- Getting 404 url while updating quantity on multiple address cart page (by @vikalps4) + * [magento/magento2#23166](https://github.com/magento/magento2/pull/23166) -- Fix 22085 (by @geet07) + * [magento/magento2#23524](https://github.com/magento/magento2/pull/23524) -- remove html tag from option html from order page (by @sunilit42) + * [magento/magento2#22891](https://github.com/magento/magento2/pull/22891) -- Check if setting is disabled on default scope (by @kassner) + * [magento/magento2#23099](https://github.com/magento/magento2/pull/23099) -- fix customer data race condition when bundling is enabled (by @davidverholen) + * [magento/magento2#23125](https://github.com/magento/magento2/pull/23125) -- Catch throwables in mview updating (by @QuentinFarizonAfrimarket) + * [magento/magento2#23173](https://github.com/magento/magento2/pull/23173) -- Fixed issue #23135: Insert Variable popup missing template variables for new templates (by @maheshWebkul721) + * [magento/magento2#23688](https://github.com/magento/magento2/pull/23688) -- Resolve "Automatically Invoice All Items" is "Yes" but no invoice is created (Zero Subtotal Checkout) (by @edenduong) + * [magento/magento2#23718](https://github.com/magento/magento2/pull/23718) -- Resolve [Authorize.net accept.js] "Place Order" button not being disabled when editing billing address (by @edenduong) + * [magento/magento2#23753](https://github.com/magento/magento2/pull/23753) -- Add Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface to di.xml in issue23717 (by @edenduong) + * [magento/magento2#22984](https://github.com/magento/magento2/pull/22984) -- magento/magento2#14071: Not able to change a position of last two rel... (by @m-a-x-i-m) + * [magento/magento2#23656](https://github.com/magento/magento2/pull/23656) -- Fixes issue 22112 (https://github.com/magento/magento2/issues/22112) ... (by @rsimmons07) + * [magento/magento2#23666](https://github.com/magento/magento2/pull/23666) -- magento/magento2#: Fix storeId param type in the EmailNotification::newAccount, EmailNotificationInterface::newAccount methods (by @atwixfirster) + * [magento/magento2#23681](https://github.com/magento/magento2/pull/23681) -- Resolve Frontend Label For Custom Order Status not Editable in Magento Admin in Single Store Mode (by @edenduong) + * [magento/magento2#23752](https://github.com/magento/magento2/pull/23752) -- [2.3] Database Media Storage : PDF Logo file now database aware (by @gwharton) + * [magento/magento2#23679](https://github.com/magento/magento2/pull/23679) -- Moved Zero Subtotal Checkout Payment Settings (by @textarea) + * [magento/magento2#23779](https://github.com/magento/magento2/pull/23779) -- Resolve "Discount Amount" field is validated after the page load without any action from user in Create New Catalog Rule form issue23777 (by @edenduong) + * [magento/magento2#23787](https://github.com/magento/magento2/pull/23787) -- Fix for PHP_CodeSniffer error after app:config:dump (by @dng-dev) + * [magento/magento2#23790](https://github.com/magento/magento2/pull/23790) -- magento/magento2#23789: CommentLevelsSniff works incorrect with @magento_import statement. (by @p-bystritsky) + * [magento/magento2#23794](https://github.com/magento/magento2/pull/23794) -- Remove duplicate declaration (by @gfernandes410) + * [magento/magento2#23803](https://github.com/magento/magento2/pull/23803) -- Resolve Toggle icon not working in create configuration Product creation Page issue 22702 (by @edenduong) + * [magento/magento2#23782](https://github.com/magento/magento2/pull/23782) -- Cleaning some code gaps (by @Stepa4man) + * [magento/magento2#23840](https://github.com/magento/magento2/pull/23840) -- Fix regular expression comment on function isNameValid() in ImageContentValidator.php (by @nimbus2300) + * [magento/magento2#23845](https://github.com/magento/magento2/pull/23845) -- Add custom added url key to decoded directive string in WYSIWYG editor (by @JeroenVanLeusden) + * [magento/magento2#23866](https://github.com/magento/magento2/pull/23866) -- additional check for correct version of sodium (by @matei) + * [magento/magento2#23901](https://github.com/magento/magento2/pull/23901) -- Resolve Report->Product->Downloads has wrong ACL issue 23900 (by @edenduong) + * [magento/magento2#23905](https://github.com/magento/magento2/pull/23905) -- Resolve No auto-focus after validation at "Create Configurations" button => User can not see the error message issue23904 (by @edenduong) + * [magento/magento2#23917](https://github.com/magento/magento2/pull/23917) -- Resolve Missing Validation at some Payment Method Settings issue 23916 (by @edenduong) + * [magento/magento2#23919](https://github.com/magento/magento2/pull/23919) -- class ApplyAttributesUpdate should use \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE instead of fixing "bundle" (by @edenduong) + * [magento/magento2#23933](https://github.com/magento/magento2/pull/23933) -- Fix display of decimal quantities for wishlist items (by @mfickers) + 2.3.2 ============= * GitHub issues: diff --git a/README.md b/README.md index ecd457a4f1aef..73154c18d891d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -[![Build Status](https://travis-ci.org/magento/magento2.svg?branch=2.3-develop)](https://travis-ci.org/magento/magento2) [![Open Source Helpers](https://www.codetriage.com/magento/magento2/badges/users.svg)](https://www.codetriage.com/magento/magento2) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magento/magento2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/magento-2/localized.svg)](https://crowdin.com/project/magento-2) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..2b06199e5f95a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Reporting Security Issues + +Magento values the contributions of the security research community, and we look forward to working with you to minimize risk to Magento merchants. + +## Where should I report security issues? + +We strongly encourage you to report all security issues privately via our [bug bounty program](https://hackerone.com/magento). Please provide us with relevant technical details and repro steps to expedite our investigation. If you prefer not to use HackerOne, email us directly at `psirt@adobe.com` with details and repro steps. + +## Learning More About Security +To learn more about securing a Magento store, please visit the [Security Center](https://magento.com/security). diff --git a/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/DisableAdminUsage.php b/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/DisableAdminUsage.php new file mode 100644 index 0000000000000..34a9ef4f75b98 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/DisableAdminUsage.php @@ -0,0 +1,104 @@ +configFactory = $configFactory; + $this->productMetadata = $productMetadata; + $this->notificationLogger = $notificationLogger; + } + + /** + * Change the value of config/admin/usage/enabled + */ + private function disableAdminUsage() + { + $configModel = $this->configFactory->create(); + $configModel->setDataByPath('admin/usage/enabled', 0); + $configModel->save(); + } + + /** + * Log information about the last admin usage selection + * + * @return ResultInterface + */ + private function markUserNotified(): ResultInterface + { + $responseContent = [ + 'success' => $this->notificationLogger->log( + $this->productMetadata->getVersion() + ), + 'error_message' => '' + ]; + + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + return $resultJson->setData($responseContent); + } + + /** + * Log information about the last shown advertisement + * + * @return ResultInterface + */ + public function execute() + { + $this->disableAdminUsage(); + $this->markUserNotified(); + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed(static::ADMIN_RESOURCE); + } +} diff --git a/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/EnableAdminUsage.php b/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/EnableAdminUsage.php new file mode 100644 index 0000000000000..f70dd57aa59d6 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Controller/Adminhtml/Config/EnableAdminUsage.php @@ -0,0 +1,102 @@ +configFactory = $configFactory; + $this->productMetadata = $productMetadata; + $this->notificationLogger = $notificationLogger; + } + + /** + * Change the value of config/admin/usage/enabled + */ + private function enableAdminUsage() + { + $configModel = $this->configFactory->create(); + $configModel->setDataByPath('admin/usage/enabled', 1); + $configModel->save(); + } + + /** + * Log information about the last user response + * + * @return ResultInterface + */ + private function markUserNotified(): ResultInterface + { + $responseContent = [ + 'success' => $this->notificationLogger->log( + $this->productMetadata->getVersion() + ), + 'error_message' => '' + ]; + + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + return $resultJson->setData($responseContent); + } + + /** + * Log information about the last shown advertisement + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $this->enableAdminUsage(); + $this->markUserNotified(); + } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed(static::ADMIN_RESOURCE); + } +} diff --git a/app/code/Magento/AdminAnalytics/Model/Condition/CanViewNotification.php b/app/code/Magento/AdminAnalytics/Model/Condition/CanViewNotification.php new file mode 100644 index 0000000000000..222261d4abfb5 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Model/Condition/CanViewNotification.php @@ -0,0 +1,84 @@ +viewerLogger = $viewerLogger; + $this->cacheStorage = $cacheStorage; + } + + /** + * Validate if notification popup can be shown and set the notification flag + * + * @param array $arguments Attributes from element node. + * @inheritdoc + */ + public function isVisible(array $arguments): bool + { + $cacheKey = self::$cachePrefix; + $value = $this->cacheStorage->load($cacheKey); + if ($value !== 'log-exists') { + $logExists = $this->viewerLogger->checkLogExists(); + if ($logExists) { + $this->cacheStorage->save('log-exists', $cacheKey); + } + return !$logExists; + } + return false; + } + + /** + * Get condition name + * + * @return string + */ + public function getName(): string + { + return self::$conditionName; + } +} diff --git a/app/code/Magento/AdminAnalytics/Model/ResourceModel/Viewer/Logger.php b/app/code/Magento/AdminAnalytics/Model/ResourceModel/Viewer/Logger.php new file mode 100644 index 0000000000000..5c225ebfeceb5 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Model/ResourceModel/Viewer/Logger.php @@ -0,0 +1,111 @@ +resource = $resource; + $this->logFactory = $logFactory; + } + + /** + * Save (insert new or update existing) log. + * + * @param string $lastViewVersion + * @return bool + */ + public function log(string $lastViewVersion): bool + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $connection */ + $connection = $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + $connection->insertOnDuplicate( + $this->resource->getTableName(self::LOG_TABLE_NAME), + [ + 'last_viewed_in_version' => $lastViewVersion, + ], + [ + 'last_viewed_in_version', + ] + ); + return true; + } + + /** + * Get log by the last view version. + * + * @return Log + */ + public function get(): Log + { + return $this->logFactory->create(['data' => $this->loadLatestLogData()]); + } + + /** + * Checks is log already exists. + * + * @return boolean + */ + public function checkLogExists(): bool + { + $data = $this->logFactory->create(['data' => $this->loadLatestLogData()]); + $lastViewedVersion = $data->getLastViewVersion(); + return isset($lastViewedVersion); + } + + /** + * Load release notification viewer log data by last view version + * + * @return array + */ + private function loadLatestLogData(): array + { + $connection = $this->resource->getConnection(); + $select = $connection->select() + ->from(['log_table' => $this->resource->getTableName(self::LOG_TABLE_NAME)]) + ->order('log_table.id desc') + ->limit(['count' => 1]); + + $data = $connection->fetchRow($select); + if (!$data) { + $data = []; + } + return $data; + } +} diff --git a/app/code/Magento/AdminAnalytics/Model/Viewer/Log.php b/app/code/Magento/AdminAnalytics/Model/Viewer/Log.php new file mode 100644 index 0000000000000..0c3b6b81ec811 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Model/Viewer/Log.php @@ -0,0 +1,36 @@ +getData('id'); + } + + /** + * Get last viewed product version + * + * @return string + */ + public function getLastViewVersion() : ?string + { + return $this->getData('last_viewed_in_version'); + } +} diff --git a/app/code/Magento/AdminAnalytics/README.md b/app/code/Magento/AdminAnalytics/README.md new file mode 100644 index 0000000000000..e905344031ad3 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/README.md @@ -0,0 +1 @@ +The Magento\AdminAnalytics module gathers information about the features Magento administrators use. This information will be used to help improve the user experience on the Magento Admin. \ No newline at end of file diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/CloseAllDialogBoxesActionGroup.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/CloseAllDialogBoxesActionGroup.xml new file mode 100644 index 0000000000000..4fd7fd17c57ee --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/CloseAllDialogBoxesActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml new file mode 100644 index 0000000000000..d9f5e5dbcb106 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml new file mode 100644 index 0000000000000..5cf7be8a6fe11 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/SelectAdminUsageSettingActionGroup.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/SelectAdminUsageSettingActionGroup.xml new file mode 100644 index 0000000000000..3b302fe5be18b --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/ActionGroup/SelectAdminUsageSettingActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminHeaderSection.xml new file mode 100644 index 0000000000000..cc9f495a60026 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminHeaderSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageConfigSection.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageConfigSection.xml new file mode 100644 index 0000000000000..634ebf855d940 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageConfigSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageNotificationSection.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageNotificationSection.xml new file mode 100644 index 0000000000000..8bd6263d35e38 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/Section/AdminUsageNotificationSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/Test/TrackingScriptTest.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/Test/TrackingScriptTest.xml new file mode 100644 index 0000000000000..58bcacc190cff --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/Test/TrackingScriptTest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + <description value="Checks to see if the tracking script is in the dom of admin and if setting is turned to no it checks if the tracking script in the dom was removed"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-18192"/> + <group value="backend"/> + <group value="login"/> + </annotations> + + <!-- Logging in Magento admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/AdminAnalytics/Test/Unit/Condition/CanViewNotificationTest.php b/app/code/Magento/AdminAnalytics/Test/Unit/Condition/CanViewNotificationTest.php new file mode 100644 index 0000000000000..7819f2f017a01 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Unit/Condition/CanViewNotificationTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AdminAnalytics\Test\Unit\Condition; + +use Magento\AdminAnalytics\Model\Condition\CanViewNotification; +use Magento\AdminAnalytics\Model\ResourceModel\Viewer\Logger; +use Magento\AdminAnalytics\Model\Viewer\Log; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\CacheInterface; + +/** + * Class CanViewNotificationTest + */ +class CanViewNotificationTest extends \PHPUnit\Framework\TestCase +{ + /** @var CanViewNotification */ + private $canViewNotification; + + /** @var Logger|\PHPUnit_Framework_MockObject_MockObject */ + private $viewerLoggerMock; + + /** @var ProductMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $productMetadataMock; + + /** @var Log|\PHPUnit_Framework_MockObject_MockObject */ + private $logMock; + + /** @var $cacheStorageMock \PHPUnit_Framework_MockObject_MockObject|CacheInterface */ + private $cacheStorageMock; + + public function setUp() + { + $this->cacheStorageMock = $this->getMockBuilder(CacheInterface::class) + ->getMockForAbstractClass(); + $this->logMock = $this->getMockBuilder(Log::class) + ->getMock(); + $this->viewerLoggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productMetadataMock = $this->getMockBuilder(ProductMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new ObjectManager($this); + $this->canViewNotification = $objectManager->getObject( + CanViewNotification::class, + [ + 'viewerLogger' => $this->viewerLoggerMock, + 'productMetadata' => $this->productMetadataMock, + 'cacheStorage' => $this->cacheStorageMock, + ] + ); + } + + /** + * @param $expected + * @param $cacheResponse + * @param $logExists + * @dataProvider isVisibleProvider + */ + public function testIsVisibleLoadDataFromLog($expected, $cacheResponse, $logExists) + { + $this->cacheStorageMock->expects($this->once()) + ->method('load') + ->with('admin-usage-notification-popup') + ->willReturn($cacheResponse); + $this->viewerLoggerMock + ->method('checkLogExists') + ->willReturn($logExists); + $this->cacheStorageMock + ->method('save') + ->with('log-exists', 'admin-usage-notification-popup'); + $this->assertEquals($expected, $this->canViewNotification->isVisible([])); + } + + /** + * @return array + */ + public function isVisibleProvider() + { + return [ + [true, false, false], + [false, 'log-exists', true], + [false, false, true], + ]; + } +} diff --git a/app/code/Magento/AdminAnalytics/Ui/DataProvider/AdminUsageNotificationDataProvider.php b/app/code/Magento/AdminAnalytics/Ui/DataProvider/AdminUsageNotificationDataProvider.php new file mode 100644 index 0000000000000..961a5663730a2 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Ui/DataProvider/AdminUsageNotificationDataProvider.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AdminAnalytics\Ui\DataProvider; + +use Magento\Ui\DataProvider\AbstractDataProvider; +use Magento\Framework\Api\Filter; + +/** + * Data Provider for the Admin usage UI component. + */ +class AdminUsageNotificationDataProvider extends AbstractDataProvider +{ + /** + * @inheritdoc + */ + public function getData() + { + return $this->data; + } + + /** + * @inheritdoc + */ + public function addFilter(Filter $filter) + { + return null; + } +} diff --git a/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php b/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php new file mode 100644 index 0000000000000..9b1accbe0c823 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\AdminAnalytics\ViewModel; + +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Backend\Model\Auth\Session; +use Magento\Framework\App\State; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Gets user version and mode + */ +class Metadata implements ArgumentInterface +{ + /** + * @var State + */ + private $appState; + + /** + * @var Session + */ + private $authSession; + + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + + /** + * @param ProductMetadataInterface $productMetadata + * @param Session $authSession + * @param State $appState + */ + public function __construct( + ProductMetadataInterface $productMetadata, + Session $authSession, + State $appState + ) { + $this->productMetadata = $productMetadata; + $this->authSession = $authSession; + $this->appState = $appState; + } + + /** + * Get product version + * + * @return string + */ + public function getMagentoVersion() :string + { + return $this->productMetadata->getVersion(); + } + + /** + * Get current user id (hash generated from email) + * + * @return string + */ + public function getCurrentUser() :string + { + return hash('sha512', 'ADMIN_USER' . $this->authSession->getUser()->getEmail()); + } + /** + * Get Magento mode that the user is using + * + * @return string + */ + public function getMode() :string + { + return $this->appState->getMode(); + } +} diff --git a/app/code/Magento/AdminAnalytics/ViewModel/Notification.php b/app/code/Magento/AdminAnalytics/ViewModel/Notification.php new file mode 100644 index 0000000000000..030e027b83fce --- /dev/null +++ b/app/code/Magento/AdminAnalytics/ViewModel/Notification.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AdminAnalytics\ViewModel; + +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\AdminAnalytics\Model\Condition\CanViewNotification as AdminAnalyticsNotification; +use Magento\ReleaseNotification\Model\Condition\CanViewNotification as ReleaseNotification; + +/** + * Control display of admin analytics and release notification modals + */ +class Notification implements ArgumentInterface +{ + /** + * @var AdminAnalyticsNotification + */ + private $canViewNotificationAnalytics; + + /** + * @var ReleaseNotification + */ + private $canViewNotificationRelease; + + /** + * @param AdminAnalyticsNotification $canViewNotificationAnalytics + * @param ReleaseNotification $canViewNotificationRelease + */ + public function __construct( + AdminAnalyticsNotification $canViewNotificationAnalytics, + ReleaseNotification $canViewNotificationRelease + ) { + $this->canViewNotificationAnalytics = $canViewNotificationAnalytics; + $this->canViewNotificationRelease = $canViewNotificationRelease; + } + + /** + * Determine if the analytics popup is visible + * + * @return bool + */ + public function isAnalyticsVisible(): bool + { + return $this->canViewNotificationAnalytics->isVisible([]); + } + + /** + * Determine if the release popup is visible + * + * @return bool + */ + public function isReleaseVisible(): bool + { + return $this->canViewNotificationRelease->isVisible([]); + } +} diff --git a/app/code/Magento/AdminAnalytics/composer.json b/app/code/Magento/AdminAnalytics/composer.json new file mode 100644 index 0000000000000..13e122d847a38 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/composer.json @@ -0,0 +1,29 @@ +{ + "name": "magento/module-admin-analytics", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "102.0.*", + "magento/module-backend": "101.0.*", + "magento/module-config": "101.1.*", + "magento/module-ui": "101.1.*", + "magento/module-release-notification": "100.3.*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AdminAnalytics\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/AdminAnalytics/etc/adminhtml/routes.xml b/app/code/Magento/AdminAnalytics/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..5b5f2b52210b0 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> + <router id="admin"> + <route id="adminAnalytics" frontName="adminAnalytics"> + <module name="Magento_AdminAnalytics" /> + </route> + </router> +</config> diff --git a/app/code/Magento/AdminAnalytics/etc/adminhtml/system.xml b/app/code/Magento/AdminAnalytics/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..d6867e74c4760 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/adminhtml/system.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="admin"> + <group id="usage" translate="label" type="text" sortOrder="2000" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Admin Usage</label> + <field id="enabled" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Enable Admin Usage Tracking</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment>Allow Magento to track admin usage in order to improve the quality and user experience.</comment> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/AdminAnalytics/etc/config.xml b/app/code/Magento/AdminAnalytics/etc/config.xml new file mode 100644 index 0000000000000..ba683f13c11e3 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/config.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <admin> + <usage> + <enabled> + 1 + </enabled> + </usage> + </admin> + </default> +</config> \ No newline at end of file diff --git a/app/code/Magento/AdminAnalytics/etc/db_schema.xml b/app/code/Magento/AdminAnalytics/etc/db_schema.xml new file mode 100644 index 0000000000000..ef1a657dc8243 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/db_schema.xml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="admin_analytics_usage_version_log" resource="default" engine="innodb" + comment="Admin Notification Viewer Log Table"> + <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Log ID"/> + <column xsi:type="varchar" name="last_viewed_in_version" nullable="false" length="50" + comment="Viewer last viewed on product version"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="id"/> + </constraint> + <constraint xsi:type="unique" referenceId="ADMIN_ANALYTICS_USAGE_VERSION_LOG_LAST_VIEWED_IN_VERSION"> + <column name="last_viewed_in_version"/> + </constraint> + </table> +</schema> diff --git a/app/code/Magento/AdminAnalytics/etc/db_schema_whitelist.json b/app/code/Magento/AdminAnalytics/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..626e3ec14bc90 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/db_schema_whitelist.json @@ -0,0 +1,12 @@ +{ + "admin_analytics_usage_version_log": { + "column": { + "id": true, + "last_viewed_in_version": true + }, + "constraint": { + "PRIMARY": true, + "ADMIN_ANALYTICS_USAGE_VERSION_LOG_LAST_VIEWED_IN_VERSION": true + } + } +} \ No newline at end of file diff --git a/app/code/Magento/AdminAnalytics/etc/module.xml b/app/code/Magento/AdminAnalytics/etc/module.xml new file mode 100644 index 0000000000000..f0990b114e25f --- /dev/null +++ b/app/code/Magento/AdminAnalytics/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_AdminAnalytics"/> +</config> diff --git a/app/code/Magento/AdminAnalytics/registration.php b/app/code/Magento/AdminAnalytics/registration.php new file mode 100644 index 0000000000000..65c9955d396a8 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AdminAnalytics', __DIR__); diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/AdminAnalytics/view/adminhtml/layout/adminhtml_dashboard_index.xml new file mode 100644 index 0000000000000..3069db1ecc2bb --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="content"> + <uiComponent name="admin_usage_notification"> + <visibilityCondition name="can_view_admin_usage_notification" className="Magento\AdminAnalytics\Model\Condition\CanViewNotification"/> + </uiComponent> + <block name="tracking_notification" as="tracking_notification" template="Magento_AdminAnalytics::notification.phtml"> + <arguments> + <argument name="notification" xsi:type="object">Magento\AdminAnalytics\ViewModel\Notification</argument> + </arguments> + </block> + </referenceContainer> + </body> +</page> \ No newline at end of file diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/layout/default.xml b/app/code/Magento/AdminAnalytics/view/adminhtml/layout/default.xml new file mode 100644 index 0000000000000..7e379a17c78d7 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/layout/default.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd" > + <body> + <referenceContainer name="header"> + <block name="tracking" as="tracking" template="Magento_AdminAnalytics::tracking.phtml" ifconfig="admin/usage/enabled"> + <arguments> + <argument name="tracking_url" xsi:type="string">//assets.adobedtm.com/launch-EN30eb7ffa064444f1b8b0368ef38fd3a9.min.js</argument> + <argument name="metadata" xsi:type="object">Magento\AdminAnalytics\ViewModel\Metadata</argument> + </arguments> + </block> + </referenceContainer> + </body> +</page> + + diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/requirejs-config.js b/app/code/Magento/AdminAnalytics/view/adminhtml/requirejs-config.js new file mode 100644 index 0000000000000..1361210929789 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_ReleaseNotification/js/modal/component': { + 'Magento_AdminAnalytics/js/release-notification/modal/component-mixin': true + } + } + } +}; diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/templates/notification.phtml b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/notification.phtml new file mode 100644 index 0000000000000..4b1f971670184 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/notification.phtml @@ -0,0 +1,16 @@ + +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> + +<script> + define('analyticsPopupConfig', function () { + return { + analyticsVisible: <?= $block->getNotification()->isAnalyticsVisible() ? 1 : 0; ?>, + releaseVisible: <?= $block->getNotification()->isReleaseVisible() ? 1 : 0; ?>, + } + }); +</script> diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml new file mode 100644 index 0000000000000..0ea5c753c9337 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> + +<script src="<?= $block->escapeUrl($block->getTrackingUrl()) ?>" async></script> +<script> + var adminAnalyticsMetadata = { + "version": "<?= $block->escapeJs($block->getMetadata()->getMagentoVersion()) ?>", + "user": "<?= $block->escapeJs($block->getMetadata()->getCurrentUser()) ?>", + "mode": "<?= $block->escapeJs($block->getMetadata()->getMode()) ?>" + }; +</script> diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/ui_component/admin_usage_notification.xml b/app/code/Magento/AdminAnalytics/view/adminhtml/ui_component/admin_usage_notification.xml new file mode 100644 index 0000000000000..3c35f1937783b --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/ui_component/admin_usage_notification.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">admin_usage_notification.admin_usage_notification_data_source</item> + </item> + <item name="label" xsi:type="string" translate="true">Admin Usage Notification</item> + <item name="template" xsi:type="string">templates/form/collapsible</item> + </argument> + <settings> + <namespace>admin_usage_notification</namespace> + <dataScope>data</dataScope> + <deps> + <dep>admin_usage_notification.admin_usage_notification_data_source</dep> + </deps> + </settings> + <dataSource name="admin_usage_notification_data_source"> + <argument name="dataProvider" xsi:type="configurableObject"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="data" xsi:type="array"> + <item name="enableLogAction" xsi:type="url" path="adminAnalytics/config/enableAdminUsage"/> + <item name="disableLogAction" xsi:type="url" path="adminAnalytics/config/disableAdminUsage"/> + + </item> + </item> + </argument> + </argument> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item> + </item> + </argument> + <dataProvider class="Magento\AdminAnalytics\Ui\DataProvider\AdminUsageNotificationDataProvider" name="admin_usage_notification_data_source"> + <settings> + <requestFieldName>id</requestFieldName> + <primaryFieldName>entity_id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <modal name="notification_modal_1" component="Magento_AdminAnalytics/js/modal/component"> + <settings> + <state>true</state> + <options> + <option name="modalClass" xsi:type="string">admin-usage-notification</option> + <option name="title" xsi:type="string" translate="true">Allow admin usage data collection</option> + <option name="autoOpen" xsi:type="boolean">true</option> + <option name="type" xsi:type="string">popup</option> + <option name="clickableOverlay" xsi:type="boolean">false</option> + <option name="responsive" xsi:type="boolean">true</option> + <option name="innerScroll" xsi:type="boolean">false</option> + <option name="buttons" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="text" xsi:type="string" translate="true">Don't Allow</item> + <item name="class" xsi:type="string">action-secondary</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="string">disableAdminUsage</item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="text" xsi:type="string" translate="true">Allow</item> + <item name="class" xsi:type="string">action-primary</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="string">enableAdminUsage</item> + </item> + </item> + </option> + </options> + </settings> + <fieldset name="notification_fieldset"> + <settings> + <label/> + </settings> + <container name="notification_text" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="additionalClasses" xsi:type="string">release-notification-text</item> + <item name="text" xsi:type="string" translate="true"><![CDATA[ + <p>Help us improve Magento Admin by allowing us to collect usage data.</p> + <p>All usage data that we collect for this purpose cannot be used to individually identify you and is used only to improve the Magento Admin and related products and services.</p> + <p>You can learn more and opt out at any time by following the instructions in <a href="https://docs.magento.com/m2/ce/user_guide/stores/admin.html" target="_blank">merchant documentation</a>.</p> +]]></item> + </item> + </argument> + </container> + </fieldset> + </modal> +</form> diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/modal/component.js b/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/modal/component.js new file mode 100644 index 0000000000000..bc09890d0d0b4 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/modal/component.js @@ -0,0 +1,116 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'jquery', + 'Magento_Ui/js/modal/modal-component', + 'uiRegistry', + 'analyticsPopupConfig' +], + function (_, $, Modal, registry, analyticsPopupConfig) { + 'use strict'; + + return Modal.extend( + { + defaults: { + imports: { + enableLogAction: '${ $.provider }:data.enableLogAction', + disableLogAction: '${ $.provider }:data.disableLogAction' + }, + options: { + keyEventHandlers: { + /** + * Prevents escape key from exiting out of modal + */ + escapeKey: function () { + return; + } + } + }, + notificationWindow: null + }, + + /** + * Initializes modal on opened function + */ + initModal: function () { + this.options.opened = this.onOpened.bind(this); + this._super(); + }, + + /** + * Once the modal is opened it hides the X + */ + onOpened: function () { + $('.modal-header button.action-close').hide(); + }, + + /** + * Changes admin usage setting to yes + */ + enableAdminUsage: function () { + var data = { + 'form_key': window.FORM_KEY + }; + + $.ajax( + { + type: 'POST', + url: this.enableLogAction, + data: data, + showLoader: true + } + ).done( + function (xhr) { + if (xhr.error) { + self.onError(xhr); + } + } + ).fail(this.onError); + this.openReleasePopup(); + this.closeModal(); + }, + + /** + * Changes admin usage setting to no + */ + disableAdminUsage: function () { + var data = { + 'form_key': window.FORM_KEY + }; + + $.ajax( + { + type: 'POST', + url: this.disableLogAction, + data: data, + showLoader: true + } + ).done( + function (xhr) { + if (xhr.error) { + self.onError(xhr); + } + } + ).fail(this.onError); + this.openReleasePopup(); + this.closeModal(); + }, + + /** + * Allows admin usage popup to be shown first and then new release notification + */ + openReleasePopup: function () { + var notifiModal = registry.get('release_notification.release_notification.notification_modal_1'); + + if (analyticsPopupConfig.releaseVisible) { + notifiModal.initializeContentAfterAnalytics(); + } + } + } + ); + } +); diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/release-notification/modal/component-mixin.js b/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/release-notification/modal/component-mixin.js new file mode 100644 index 0000000000000..ffecd031cbb43 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/web/js/release-notification/modal/component-mixin.js @@ -0,0 +1,39 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define(['jquery', 'analyticsPopupConfig'], function ($, analyticsPopupConfig) { + 'use strict'; + + var deferred = $.Deferred(), + + mixin = { + /** + * Initializes content only if its visible + */ + initializeContent: function () { + var initializeContent = this._super.bind(this); + + if (!analyticsPopupConfig.analyticsVisible) { + initializeContent(); + } else { + deferred.then(function () { + initializeContent(); + }); + } + }, + + /** + * Initializes release notification content after admin analytics + */ + initializeContentAfterAnalytics: function () { + deferred.resolve(); + } + }; + + return function (target) { + return target.extend(mixin); + }; +}); + diff --git a/app/code/Magento/AdminNotification/Model/Feed.php b/app/code/Magento/AdminNotification/Model/Feed.php index d3b0b8501c864..b99a8bbbc9031 100644 --- a/app/code/Magento/AdminNotification/Model/Feed.php +++ b/app/code/Magento/AdminNotification/Model/Feed.php @@ -5,6 +5,8 @@ */ namespace Magento\AdminNotification\Model; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\ConfigOptionsListConstants; /** @@ -25,6 +27,11 @@ class Feed extends \Magento\Framework\Model\AbstractModel const XML_LAST_UPDATE_PATH = 'system/adminnotification/last_update'; + /** + * @var Escaper + */ + private $escaper; + /** * Feed url * @@ -77,6 +84,7 @@ class Feed extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param Escaper|null $escaper * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -90,21 +98,26 @@ public function __construct( \Magento\Framework\UrlInterface $urlBuilder, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + Escaper $escaper = null ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); - $this->_backendConfig = $backendConfig; - $this->_inboxFactory = $inboxFactory; - $this->curlFactory = $curlFactory; + $this->_backendConfig = $backendConfig; + $this->_inboxFactory = $inboxFactory; + $this->curlFactory = $curlFactory; $this->_deploymentConfig = $deploymentConfig; - $this->productMetadata = $productMetadata; - $this->urlBuilder = $urlBuilder; + $this->productMetadata = $productMetadata; + $this->urlBuilder = $urlBuilder; + $this->escaper = $escaper ?? ObjectManager::getInstance()->get( + Escaper::class + ); } /** * Init model * * @return void + * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ protected function _construct() { @@ -252,6 +265,6 @@ public function getFeedXml() */ private function escapeString(\SimpleXMLElement $data) { - return htmlspecialchars((string)$data); + return $this->escaper->escapeHtml((string)$data); } } diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index a397ceaccf7a9..8fe3c3b2ab20c 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -5,13 +5,14 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "lib-libxml": "*", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-media-storage": "100.3.*", "magento/module-store": "101.0.*", - "magento/module-ui": "101.1.*" + "magento/module-ui": "101.1.*", + "magento/module-config": "101.1.*" }, "type": "magento2-module", "license": [ @@ -26,5 +27,5 @@ "Magento\\AdminNotification\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml index 3f79e803ccca2..b4f19bda36cbf 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/notification/window.phtml @@ -4,10 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - -?> -<?php /** * @see \Magento\AdminNotification\Block\Window */ @@ -19,11 +15,13 @@ "autoOpen": true, "buttons": false, "modalClass": "modal-system-messages", - "title": "<?= /* @escapeNotVerified */ $block->getHeaderText() ?>" + "title": "<?= $block->escapeHtmlAttr($block->getHeaderText()) ?>" } }'> <li class="message message-warning warning"> - <?= /* @escapeNotVerified */ $block->getNoticeMessageText() ?><br/> - <a href="<?= /* @escapeNotVerified */ $block->getNoticeMessageUrl() ?>"><?= /* @escapeNotVerified */ $block->getReadDetailsText() ?></a> + <?= $block->escapeHtml($block->getNoticeMessageText()) ?><br/> + <a href="<?= $block->escapeUrl($block->getNoticeMessageUrl()) ?>"> + <?= $block->escapeHtml($block->getReadDetailsText()) ?> + </a> </li> </ul> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml index 01d6fdcb29571..22512b9055f95 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages.phtml @@ -4,41 +4,41 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\AdminNotification\Block\System\Messages */ ?> -<?php /** @var $block \Magento\AdminNotification\Block\System\Messages */ ?> <?php $lastCritical = $block->getLastCritical();?> -<div id="system_messages" class="message-system<?php if ($lastCritical): ?> message-system-unread<?php endif; ?>"> +<div id="system_messages" + class="message-system<?php if ($lastCritical) : ?> + message-system-unread<?php endif; ?>"> <div class="message-system-inner"> - <?php if ($lastCritical): ?> + <?php if ($lastCritical) : ?> <ul class="message-system-list"> <li class="message message-warning error"> - <?= /* @escapeNotVerified */ $lastCritical->getText() ?> + <?= $block->escapeHtml($lastCritical->getText()) ?> </li> </ul> <?php endif; ?> <div class="message-system-short"> <span class="message-system-short-label"> - <?= /* @escapeNotVerified */ __('System Messages:') ?> + <?= $block->escapeHtml(__('System Messages:')) ?> </span> - <?php if ($block->getCriticalCount()): ?> + <?php if ($block->getCriticalCount()) : ?> <div class="message message-warning error"> <a class="message-link" href="#" title="<?= $block->escapeHtml(__('Critical System Messages')) ?>"> - <?= /* @escapeNotVerified */ $block->getCriticalCount() ?> + <?= (int) $block->getCriticalCount() ?> </a> </div> - <?php endif;?> + <?php endif; ?> - <?php if ($block->getMajorCount()): ?> + <?php if ($block->getMajorCount()) : ?> <div class="message message-warning warning"> <a class="message-link" href="#" title="<?= $block->escapeHtml(__('Major System Messages')) ?>"> - <?= /* @escapeNotVerified */ $block->getMajorCount() ?> + <?= (int) $block->getMajorCount() ?> </a> </div> - <?php endif;?> + <?php endif; ?> </div> <div id="message-system-all" title="<?= $block->escapeHtml(__('System messages')) ?>" data-mage-init='<?= $block->escapeHtml($block->getSystemMessageDialogJson()) ?>'></div> </div> 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 0448daaf17644..494e60865623b 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 @@ -4,16 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\AdminNotification\Block\System\Messages\UnreadMessagePopup */ ?> -<?php /** @var $block \Magento\AdminNotification\Block\System\Messages\UnreadMessagePopup */ ?> -<div style="display:none" id="system_messages_list" data-role="system_messages_list" title="<?= $block->escapeHtml($block->getPopupTitle()) ?>"> +<div style="display:none" id="system_messages_list" data-role="system_messages_list" + title="<?= $block->escapeHtmlAttr($block->getPopupTitle()) ?>"> <ul class="message-system-list messages"> - <?php foreach ($block->getUnreadMessages() as $message): ?> - <li class="message message-warning <?= /* @escapeNotVerified */ $block->getItemClass($message) ?>"> - <?= /* @escapeNotVerified */ $message->getText() ?> + <?php foreach ($block->getUnreadMessages() as $message) : ?> + <li class="message message-warning <?= $block->escapeHtmlAttr($block->getItemClass($message)) ?>"> + <?= $block->escapeHtml($message->getText()) ?> </li> <?php endforeach;?> </ul> @@ -27,4 +26,4 @@ } } } -</script> \ No newline at end of file +</script> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/toolbar_entry.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/toolbar_entry.phtml index 777448cdd82b4..38398727e0f90 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/toolbar_entry.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/toolbar_entry.phtml @@ -4,81 +4,78 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +/** @var $this \Magento\AdminNotification\Block\ToolbarEntry */ -?> -<?php /** @var $this \Magento\AdminNotification\Block\ToolbarEntry */ ?> -<?php $notificationCount = $block->getUnreadNotificationCount(); $notificationCounterMax = $block->getNotificationCounterMax(); ?> <div data-mage-init='{"toolbarEntry": {}}' class="notifications-wrapper admin__action-dropdown-wrap" - data-notification-count="<?= /* @escapeNotVerified */ $notificationCount ?>"> + data-notification-count="<?= (int)$notificationCount ?>"> <?php if ($notificationCount > 0) : ?> <a - href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/notification/index') ?>" + href="<?= $block->escapeUrl($block->getUrl('adminhtml/notification/index')) ?>" class="notifications-action admin__action-dropdown" data-mage-init='{"dropdown":{}}' - title="<?= /* @escapeNotVerified */ __('Notifications') ?>" + title="<?= $block->escapeHtmlAttr(__('Notifications')) ?>" data-toggle="dropdown"> <span class="notifications-counter"> - <?= /* @escapeNotVerified */ ($notificationCount > $notificationCounterMax) ? $notificationCounterMax . '+' : $notificationCount ?> + <?= /* @noEscape */ ($notificationCount > $notificationCounterMax) ? (int)$notificationCounterMax . '+' : (int)$notificationCount ?> </span> </a> <ul class="admin__action-dropdown-menu" - data-mark-as-read-url="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/notification/ajaxMarkAsRead') ?>"> + data-mark-as-read-url="<?= $block->escapeUrl($block->getUrl('adminhtml/notification/ajaxMarkAsRead')) ?>"> <?php foreach ($block->getLatestUnreadNotifications() as $notification) : ?> - <?php /** @var $notification \Magento\AdminNotification\Model\Inbox*/ ?> - <li class="notifications-entry<?php if ($notification->getSeverity() == 1): ?> notifications-critical<?php endif; ?>" - data-notification-id="<?= /* @escapeNotVerified */ $notification->getId() ?>" - data-notification-severity="<?php if ($notification->getSeverity() == 1): ?>1<?php endif; ?>"> - <?php - $notificationDescription = $block->escapeHtml($notification->getDescription()); - $notificationDescriptionLength = $block->getNotificationDescriptionLength(); - ?> - <strong class="notifications-entry-title"> - <?= $block->escapeHtml($notification->getTitle()) ?> - </strong> - <?php if (strlen($notificationDescription) > $notificationDescriptionLength) : ?> - <p class="notifications-entry-description _cutted"> - <span class="notifications-entry-description-start"> - <?= /* @escapeNotVerified */ substr($notificationDescription, 0, $notificationDescriptionLength) ?> - </span> - <span class="notifications-entry-description-end"> - <?= /* @escapeNotVerified */ substr($notificationDescription, $notificationDescriptionLength) ?> - </span> - </p> - <?php else : ?> - <p class="notifications-entry-description"> - <?= /* @escapeNotVerified */ $notificationDescription ?> - </p> - <?php endif; ?> - <time class="notifications-entry-time"> - <?= /* @escapeNotVerified */ $block->formatNotificationDate($notification->getDateAdded()) ?> - </time> - <button - type="button" - class="notifications-close" - title="<?= /* @escapeNotVerified */ __('Close') ?>" - ></button> - </li> + <?php /** @var $notification \Magento\AdminNotification\Model\Inbox */ ?> + <li class="notifications-entry<?php if ($notification->getSeverity() == 1) : ?> notifications-critical<?php endif; ?>" + data-notification-id="<?= $block->escapeHtmlAttr($notification->getId()) ?>" + data-notification-severity="<?php if ($notification->getSeverity() == 1) : ?>1<?php endif; ?>"> + <?php + $notificationDescription = $notification->getDescription(); + $notificationDescriptionLength = $block->getNotificationDescriptionLength(); + ?> + <strong class="notifications-entry-title"> + <?= $block->escapeHtml($notification->getTitle()) ?> + </strong> + <?php if (strlen($notificationDescription) > $notificationDescriptionLength) : ?> + <p class="notifications-entry-description _cutted"> + <span class="notifications-entry-description-start"> + <?= $block->escapeHtml(substr($notificationDescription, 0, $notificationDescriptionLength)) ?> + </span> + <span class="notifications-entry-description-end"> + <?= $block->escapeHtml(substr($notificationDescription, $notificationDescriptionLength)) ?> + </span> + </p> + <?php else : ?> + <p class="notifications-entry-description"> + <?= $block->escapeHtml($notificationDescription) ?> + </p> + <?php endif; ?> + <time class="notifications-entry-time"> + <?= $block->escapeHtml($block->formatNotificationDate($notification->getDateAdded())) ?> + </time> + <button + type="button" + class="notifications-close" + title="<?= $block->escapeHtmlAttr(__('Close')) ?>" + ></button> + </li> <?php endforeach; ?> <li class="notifications-entry notifications-entry-last"> <a - href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/notification/index') ?>" + href="<?= $block->escapeUrl($block->getUrl('adminhtml/notification/index')) ?>" class="action-tertiary action-more"> - <?= /* @escapeNotVerified */ __('See All (') ?><span class="notifications-counter"><?= /* @escapeNotVerified */ $notificationCount ?></span><?= /* @escapeNotVerified */ __(' unread)') ?> + <?= $block->escapeHtml(__('See All (')) ?><span class="notifications-counter"><?= (int)$notificationCount ?></span><?= $block->escapeHtml(__(' unread)')) ?> </a> </li> </ul> <?php else : ?> <a class="notifications-action admin__action-dropdown" - href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/notification/index') ?>" - title="<?= /* @escapeNotVerified */ __('Notifications') ?>"> + href="<?= $block->escapeUrl($block->getUrl('adminhtml/notification/index')) ?>" + title="<?= $block->escapeHtmlAttr(__('Notifications')) ?>"> </a> <?php endif; ?> </div> diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index e36a8cd7963ed..d929a029f29dd 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-catalog-import-export": "101.0.*", @@ -28,5 +28,5 @@ "Magento\\AdvancedPricingImportExport\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json index 03f7b5d1a75f8..1f55e2b4cba5f 100644 --- a/app/code/Magento/AdvancedSearch/composer.json +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -13,7 +13,7 @@ "magento/module-customer": "102.0.*", "magento/module-search": "101.0.*", "magento/module-store": "101.0.*", - "php": "~7.1.3||~7.2.0" + "php": "~7.1.3||~7.2.0||~7.3.0" }, "type": "magento2-module", "license": [ @@ -28,5 +28,5 @@ "Magento\\AdvancedSearch\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml b/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml index ae202cbfaf442..a583b2d51e461 100644 --- a/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/templates/system/config/testconnection.phtml @@ -3,13 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile ?> <button class="scalable" type="button" id="<?= $block->getHtmlId() ?>" data-mage-init='{"testConnection":{ - "url": "<?= /* @escapeNotVerified */ $block->getAjaxUrl() ?>", + "url": "<?= $block->escapeUrl($block->getAjaxUrl()) ?>", "elementId": "<?= $block->getHtmlId() ?>", - "successText": "<?= /* @escapeNotVerified */ __('Successful! Test again?') ?>", - "failedText": "<?= /* @escapeNotVerified */ __('Connection failed! Test again?') ?>", - "fieldMapping": "<?= /* @escapeNotVerified */ $block->getFieldMapping() ?>"}, "validation": {}}'> - <span><span><span id="<?= $block->getHtmlId() ?>_result"><?= $block->escapeHtml($block->getButtonLabel()) ?></span></span></span> + "successText": "<?= $block->escapeHtmlAttr(__('Successful! Test again?')) ?>", + "failedText": "<?= $block->escapeHtmlAttr(__('Connection failed! Test again?')) ?>", + "fieldMapping": "<?= /* @noEscape */ $block->getFieldMapping() ?>"}, "validation": {}}'> + <span id="<?= $block->getHtmlId() ?>_result"><?= $block->escapeHtml($block->getButtonLabel()) ?></span> </button> diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js index e28f1b4d07d94..ba08b8ecd291b 100644 --- a/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/web/js/testconnection.js @@ -40,7 +40,8 @@ define([ element = $('#' + this.options.elementId), self = this, params = {}, - msg = ''; + msg = '', + fieldToCheck = this.options.fieldToCheck || 'success'; element.removeClass('success').addClass('fail'); $.each($.parseJSON(this.options.fieldMapping), function (key, el) { @@ -49,9 +50,10 @@ define([ $.ajax({ url: this.options.url, showLoader: true, - data: params + data: params, + headers: this.options.headers || {} }).done(function (response) { - if (response.success) { + if (response[fieldToCheck]) { element.removeClass('fail').addClass('success'); result = self.options.successText; } else { diff --git a/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml b/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml index 6e660555053a1..1fa23cf3d624a 100644 --- a/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml +++ b/app/code/Magento/AdvancedSearch/view/frontend/templates/search_data.phtml @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + /** * @var \Magento\AdvancedSearch\Block\SearchData $block */ @@ -11,15 +11,15 @@ <?php /** @var \Magento\Search\Model\QueryResult[] $data */ $data = $block->getItems(); -if (count($data)):?> +if (count($data)) : ?> <dl class="block"> - <dt class="title"><?= /* @escapeNotVerified */ __($block->getTitle()) ?></dt> + <dt class="title"><?= $block->escapeHtml(__($block->getTitle())) ?></dt> <?php foreach ($data as $additionalInfo) : ?> <dd class="item"> - <a href="<?= /* @escapeNotVerified */ $block->getLink($additionalInfo->getQueryText()) ?>" + <a href="<?= $block->escapeUrl($block->getLink($additionalInfo->getQueryText())) ?>" ><?= $block->escapeHtml($additionalInfo->getQueryText()) ?></a> - <?php if ($block->isShowResultsCount()): ?> - <span class="count"><?= /* @escapeNotVerified */ $additionalInfo->getResultsCount() ?></span> + <?php if ($block->isShowResultsCount()) : ?> + <span class="count"><?= /* @noEscape */ (int)$additionalInfo->getResultsCount() ?></span> <?php endif; ?> </dd> <?php endforeach; ?> diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json index 7d18c885cbf19..4cbab04b41765 100644 --- a/app/code/Magento/Amqp/composer.json +++ b/app/code/Magento/Amqp/composer.json @@ -8,7 +8,7 @@ "magento/framework": "102.0.*", "magento/framework-amqp": "100.3.*", "magento/framework-message-queue": "100.3.*", - "php": "~7.1.3||~7.2.0" + "php": "~7.1.3||~7.2.0||~7.3.0" }, "type": "magento2-module", "license": [ @@ -23,5 +23,5 @@ "Magento\\Amqp\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/AmqpStore/LICENSE.txt b/app/code/Magento/AmqpStore/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/AmqpStore/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 <insert your license name here>" 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/AmqpStore/LICENSE_AFL.txt b/app/code/Magento/AmqpStore/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/AmqpStore/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 <insert your license name here>" 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/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php b/app/code/Magento/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php new file mode 100644 index 0000000000000..e05004cea78cd --- /dev/null +++ b/app/code/Magento/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AmqpStore\Plugin\AsynchronousOperations; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\MessageQueue\EnvelopeFactory; +use PhpAmqpLib\Wire\AMQPTable; +use Magento\Framework\MessageQueue\EnvelopeInterface; +use Magento\AsynchronousOperations\Model\MassConsumerEnvelopeCallback as SubjectMassConsumerEnvelopeCallback; +use Psr\Log\LoggerInterface; + +/** + * Plugin to get 'store_id' from the new custom header 'store_id' in amqp + * 'application_headers' properties and setCurrentStore by value 'store_id'. + */ +class MassConsumerEnvelopeCallback +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var EnvelopeFactory + */ + private $envelopeFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param EnvelopeFactory $envelopeFactory + * @param StoreManagerInterface $storeManager + * @param LoggerInterface $logger + */ + public function __construct( + EnvelopeFactory $envelopeFactory, + StoreManagerInterface $storeManager, + LoggerInterface $logger + ) { + $this->storeManager = $storeManager; + $this->envelopeFactory = $envelopeFactory; + $this->logger = $logger; + } + + /** + * Check if amqpProperties['application_headers'] have 'store_id' and use it to setCurrentStore + * Restore original store value in consumer process after execution. + * Reject queue messages because of wrong store_id. + * + * @param SubjectMassConsumerEnvelopeCallback $subject + * @param callable $proceed + * @param EnvelopeInterface $message + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + SubjectMassConsumerEnvelopeCallback $subject, + callable $proceed, + EnvelopeInterface $message + ) { + $amqpProperties = $message->getProperties(); + if (isset($amqpProperties['application_headers'])) { + $headers = $amqpProperties['application_headers']; + if ($headers instanceof AMQPTable) { + $headers = $headers->getNativeData(); + } + if (isset($headers['store_id'])) { + $storeId = $headers['store_id']; + try { + $currentStoreId = $this->storeManager->getStore()->getId(); + } catch (NoSuchEntityException $e) { + $this->logger->error( + sprintf( + "Can't set currentStoreId during processing queue. Message rejected. Error %s.", + $e->getMessage() + ) + ); + $subject->getQueue()->reject($message, false, $e->getMessage()); + return; + } + if (isset($storeId) && $storeId !== $currentStoreId) { + $this->storeManager->setCurrentStore($storeId); + } + } + } + $proceed($message); + if (isset($storeId, $currentStoreId) && $storeId !== $currentStoreId) { + $this->storeManager->setCurrentStore($currentStoreId);//restore original store value + } + } +} diff --git a/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php b/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php new file mode 100644 index 0000000000000..c5db1f5300c29 --- /dev/null +++ b/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php @@ -0,0 +1,117 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AmqpStore\Plugin\Framework\Amqp\Bulk; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\MessageQueue\EnvelopeFactory; +use PhpAmqpLib\Exception\AMQPInvalidArgumentException; +use PhpAmqpLib\Wire\AMQPTable; +use Magento\Framework\Amqp\Bulk\Exchange as SubjectExchange; +use Magento\Framework\MessageQueue\EnvelopeInterface; +use Psr\Log\LoggerInterface; + +/** + * Plugin to set 'store_id' to the new custom header 'store_id' in amqp + * 'application_headers'. + */ +class Exchange +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var EnvelopeFactory + */ + private $envelopeFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * Exchange constructor. + * @param EnvelopeFactory $envelopeFactory + * @param StoreManagerInterface $storeManager + * @param LoggerInterface $logger + */ + public function __construct( + EnvelopeFactory $envelopeFactory, + StoreManagerInterface $storeManager, + LoggerInterface $logger + ) { + $this->storeManager = $storeManager; + $this->envelopeFactory = $envelopeFactory; + $this->logger = $logger; + } + + /** + * Set current store_id in amqpProperties['application_headers'] + * so consumer may check store_id and execute operation in correct store scope. + * Prevent publishing inconsistent messages because of store_id not defined or wrong. + * + * @param SubjectExchange $subject + * @param string $topic + * @param EnvelopeInterface[] $envelopes + * @return array + * @throws AMQPInvalidArgumentException + * @throws \LogicException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeEnqueue(SubjectExchange $subject, $topic, array $envelopes) + { + try { + $storeId = $this->storeManager->getStore()->getId(); + } catch (NoSuchEntityException $e) { + $errorMessage = sprintf( + "Can't get current storeId and inject to amqp message. Error %s.", + $e->getMessage() + ); + $this->logger->error($errorMessage); + throw new \LogicException($errorMessage); + } + + $updatedEnvelopes = []; + foreach ($envelopes as $envelope) { + $properties = $envelope->getProperties(); + if (!isset($properties)) { + $properties = []; + } + if (isset($properties['application_headers'])) { + $headers = $properties['application_headers']; + if ($headers instanceof AMQPTable) { + try { + $headers->set('store_id', $storeId); + // phpcs:ignore Magento2.Exceptions.ThrowCatch + } catch (AMQPInvalidArgumentException $ea) { + $errorMessage = sprintf("Can't set storeId to amqp message. Error %s.", $ea->getMessage()); + $this->logger->error($errorMessage); + throw new AMQPInvalidArgumentException($errorMessage); + } + $properties['application_headers'] = $headers; + } + } else { + $properties['application_headers'] = new AMQPTable(['store_id' => $storeId]); + } + $updatedEnvelopes[] = $this->envelopeFactory->create( + [ + 'body' => $envelope->getBody(), + 'properties' => $properties + ] + ); + } + if (!empty($updatedEnvelopes)) { + $envelopes = $updatedEnvelopes; + } + return [$topic, $envelopes]; + } +} diff --git a/app/code/Magento/AmqpStore/README.md b/app/code/Magento/AmqpStore/README.md new file mode 100644 index 0000000000000..0f84c8ff3276e --- /dev/null +++ b/app/code/Magento/AmqpStore/README.md @@ -0,0 +1,3 @@ +# Amqp Store + +**AmqpStore** provides ability to specify store before publish messages with Amqp. diff --git a/app/code/Magento/AmqpStore/composer.json b/app/code/Magento/AmqpStore/composer.json new file mode 100644 index 0000000000000..94189333c267b --- /dev/null +++ b/app/code/Magento/AmqpStore/composer.json @@ -0,0 +1,31 @@ +{ + "name": "magento/module-amqp-store", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "magento/framework": "102.0.*", + "magento/framework-amqp": "100.3.*", + "magento/module-store": "101.0.*", + "php": "~7.1.3||~7.2.0||~7.3.0" + }, + "suggest": { + "magento/module-asynchronous-operations": "100.3.*", + "magento/framework-message-queue": "100.3.*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AmqpStore\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/AmqpStore/etc/di.xml b/app/code/Magento/AmqpStore/etc/di.xml new file mode 100644 index 0000000000000..3bbbebd249535 --- /dev/null +++ b/app/code/Magento/AmqpStore/etc/di.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\Amqp\Bulk\Exchange"> + <plugin name="amqpStoreIdFieldForAmqpBulkExchange" type="Magento\AmqpStore\Plugin\Framework\Amqp\Bulk\Exchange"/> + </type> + <type name="Magento\AsynchronousOperations\Model\MassConsumerEnvelopeCallback"> + <plugin name="amqpStoreIdFieldForAsynchronousOperationsMassConsumerEnvelopeCallback" type="Magento\AmqpStore\Plugin\AsynchronousOperations\MassConsumerEnvelopeCallback"/> + </type> +</config> diff --git a/app/code/Magento/AmqpStore/etc/module.xml b/app/code/Magento/AmqpStore/etc/module.xml new file mode 100644 index 0000000000000..085b97b5e2f96 --- /dev/null +++ b/app/code/Magento/AmqpStore/etc/module.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_AmqpStore"> + <sequence> + <module name="Magento_Store"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/AmqpStore/registration.php b/app/code/Magento/AmqpStore/registration.php new file mode 100644 index 0000000000000..4922879bfbf16 --- /dev/null +++ b/app/code/Magento/AmqpStore/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AmqpStore', __DIR__); diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php index 57b61c1b5562a..e32b37ef239c9 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php @@ -31,8 +31,9 @@ public function __construct(ConverterInterface $converter, array $responseHandle } /** - * @param \Zend_Http_Response $response + * Get result from $response. * + * @param \Zend_Http_Response $response * @return bool|string */ public function getResult(\Zend_Http_Response $response) @@ -41,7 +42,8 @@ public function getResult(\Zend_Http_Response $response) $converterMediaType = $this->converter->getContentMediaType(); /** Content-Type header may not only contain media-type declaration */ - if ($response->getBody() && is_int(strripos($response->getHeader('Content-Type'), $converterMediaType))) { + if ($response->getBody() + && is_int(strripos($response->getHeader('Content-Type'), (string) $converterMediaType))) { $responseBody = $this->converter->fromBody($response->getBody()); } else { $responseBody = []; diff --git a/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml index 2e5f2b762a7b1..36e4779a48d42 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml @@ -14,8 +14,8 @@ <element name="advancedReportingIndustry" type="select" selector="#analytics_general_vertical"/> <element name="advancedReportingIndustryLabel" type="text" selector=".config-vertical-label>label>span"/> <element name="advancedReportingHour" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(2)"/> - <element name="advancedReportingMinute" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(3)"/> - <element name="advancedReportingSeconds" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(4)"/> + <element name="advancedReportingMinute" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(4)"/> + <element name="advancedReportingSeconds" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(6)"/> <element name="advancedReportingBlankIndustryError" type="text" selector=".message-error>div"/> </section> </sections> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml index 58e62500b8203..8ebd8cb594bee 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -25,9 +25,9 @@ <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> - <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingHour}}" userInput="11" stepKey="selectAdvancedReportingHour"/> - <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingMinute}}" userInput="11" stepKey="selectAdvancedReportingMinute"/> - <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingSeconds}}" userInput="00" stepKey="selectAdvancedReportingSeconds"/> + <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingHour}}" userInput="23" stepKey="selectAdvancedReportingHour"/> + <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingMinute}}" userInput="59" stepKey="selectAdvancedReportingMinute"/> + <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingSeconds}}" userInput="59" stepKey="selectAdvancedReportingSeconds"/> <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButton"/> <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeSuccess"/> </test> diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php index f314d77f32b41..5288bcd306af9 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php @@ -10,6 +10,9 @@ use Magento\Framework\Module\Manager as ModuleManager; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +/** + * Module iterator test. + */ class ModuleIteratorTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json index b20f3c81b1769..a62cdbb0bb9a1 100644 --- a/app/code/Magento/Analytics/composer.json +++ b/app/code/Magento/Analytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-analytics", "description": "N/A", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-backend": "101.0.*", "magento/module-config": "101.1.*", "magento/module-integration": "100.3.*", @@ -22,5 +22,5 @@ "Magento\\Analytics\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml index a22c603b2a8b3..b056ffce1fa3d 100644 --- a/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml +++ b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile ?> <section class="dashboard-advanced-reports" data-index="dashboard-advanced-reports"> diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php index af1ef4400e442..e3ba8b0681971 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php @@ -8,18 +8,11 @@ namespace Magento\AsynchronousOperations\Model; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Registry; -use Psr\Log\LoggerInterface; -use Magento\Framework\MessageQueue\MessageLockException; -use Magento\Framework\MessageQueue\ConnectionLostException; -use Magento\Framework\Exception\NotFoundException; use Magento\Framework\MessageQueue\CallbackInvokerInterface; use Magento\Framework\MessageQueue\ConsumerConfigurationInterface; use Magento\Framework\MessageQueue\EnvelopeInterface; use Magento\Framework\MessageQueue\QueueInterface; -use Magento\Framework\MessageQueue\LockInterface; -use Magento\Framework\MessageQueue\MessageController; use Magento\Framework\MessageQueue\ConsumerInterface; /** @@ -34,64 +27,38 @@ class MassConsumer implements ConsumerInterface */ private $invoker; - /** - * @var \Magento\Framework\App\ResourceConnection - */ - private $resource; - /** * @var \Magento\Framework\MessageQueue\ConsumerConfigurationInterface */ private $configuration; /** - * @var \Magento\Framework\MessageQueue\MessageController - */ - private $messageController; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var OperationProcessor + * @var Registry */ - private $operationProcessor; + private $registry; /** - * @var Registry + * @var MassConsumerEnvelopeCallbackFactory */ - private $registry; + private $massConsumerEnvelopeCallback; /** * Initialize dependencies. * * @param CallbackInvokerInterface $invoker - * @param ResourceConnection $resource - * @param MessageController $messageController * @param ConsumerConfigurationInterface $configuration - * @param OperationProcessorFactory $operationProcessorFactory - * @param LoggerInterface $logger + * @param MassConsumerEnvelopeCallbackFactory $massConsumerEnvelopeCallback * @param Registry $registry */ public function __construct( CallbackInvokerInterface $invoker, - ResourceConnection $resource, - MessageController $messageController, ConsumerConfigurationInterface $configuration, - OperationProcessorFactory $operationProcessorFactory, - LoggerInterface $logger, + MassConsumerEnvelopeCallbackFactory $massConsumerEnvelopeCallback, Registry $registry = null ) { $this->invoker = $invoker; - $this->resource = $resource; - $this->messageController = $messageController; $this->configuration = $configuration; - $this->operationProcessor = $operationProcessorFactory->create([ - 'configuration' => $configuration - ]); - $this->logger = $logger; + $this->massConsumerEnvelopeCallback = $massConsumerEnvelopeCallback; $this->registry = $registry ?? \Magento\Framework\App\ObjectManager::getInstance() ->get(Registry::class); } @@ -122,38 +89,14 @@ public function process($maxNumberOfMessages = null) */ private function getTransactionCallback(QueueInterface $queue) { - return function (EnvelopeInterface $message) use ($queue) { - /** @var LockInterface $lock */ - $lock = null; - try { - $topicName = $message->getProperties()['topic_name']; - $lock = $this->messageController->lock($message, $this->configuration->getConsumerName()); - - $allowedTopics = $this->configuration->getTopicNames(); - if (in_array($topicName, $allowedTopics)) { - $this->operationProcessor->process($message->getBody()); - } else { - $queue->reject($message); - return; - } - $queue->acknowledge($message); - } catch (MessageLockException $exception) { - $queue->acknowledge($message); - } catch (ConnectionLostException $e) { - if ($lock) { - $this->resource->getConnection() - ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); - } - } catch (NotFoundException $e) { - $queue->acknowledge($message); - $this->logger->warning($e->getMessage()); - } catch (\Exception $e) { - $queue->reject($message, false, $e->getMessage()); - if ($lock) { - $this->resource->getConnection() - ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); - } - } + $callbackInstance = $this->massConsumerEnvelopeCallback->create( + [ + 'configuration' => $this->configuration, + 'queue' => $queue, + ] + ); + return function (EnvelopeInterface $message) use ($callbackInstance) { + $callbackInstance->execute($message); }; } } diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumerEnvelopeCallback.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumerEnvelopeCallback.php new file mode 100644 index 0000000000000..42437292e6f00 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumerEnvelopeCallback.php @@ -0,0 +1,137 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\Framework\App\ResourceConnection; +use Psr\Log\LoggerInterface; +use Magento\Framework\MessageQueue\MessageLockException; +use Magento\Framework\MessageQueue\ConnectionLostException; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\MessageQueue\ConsumerConfigurationInterface; +use Magento\Framework\MessageQueue\EnvelopeInterface; +use Magento\Framework\MessageQueue\QueueInterface; +use Magento\Framework\MessageQueue\LockInterface; +use Magento\Framework\MessageQueue\MessageController; + +/** + * Class used as public callback function by async consumer. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MassConsumerEnvelopeCallback +{ + /** + * @var QueueInterface + */ + private $queue; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var ConsumerConfigurationInterface + */ + private $configuration; + + /** + * @var MessageController + */ + private $messageController; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var OperationProcessor + */ + private $operationProcessor; + + /** + * @param ResourceConnection $resource + * @param MessageController $messageController + * @param ConsumerConfigurationInterface $configuration + * @param OperationProcessorFactory $operationProcessorFactory + * @param LoggerInterface $logger + * @param QueueInterface $queue + */ + public function __construct( + ResourceConnection $resource, + MessageController $messageController, + ConsumerConfigurationInterface $configuration, + OperationProcessorFactory $operationProcessorFactory, + LoggerInterface $logger, + QueueInterface $queue + ) { + $this->resource = $resource; + $this->messageController = $messageController; + $this->configuration = $configuration; + $this->operationProcessor = $operationProcessorFactory->create( + [ + 'configuration' => $configuration + ] + ); + $this->logger = $logger; + $this->queue = $queue; + } + + /** + * Get transaction callback. This handles the case of async. + * + * @param EnvelopeInterface $message + * @return void + */ + public function execute(EnvelopeInterface $message) + { + $queue = $this->queue; + /** @var LockInterface $lock */ + $lock = null; + try { + $topicName = $message->getProperties()['topic_name']; + $lock = $this->messageController->lock($message, $this->configuration->getConsumerName()); + + $allowedTopics = $this->configuration->getTopicNames(); + if (in_array($topicName, $allowedTopics)) { + $this->operationProcessor->process($message->getBody()); + } else { + $queue->reject($message); + return; + } + $queue->acknowledge($message); + } catch (MessageLockException $exception) { + $queue->acknowledge($message); + } catch (ConnectionLostException $e) { + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } catch (NotFoundException $e) { + $queue->acknowledge($message); + $this->logger->warning($e->getMessage()); + } catch (\Exception $e) { + $queue->reject($message, false, $e->getMessage()); + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } + } + + /** + * Get message queue. + * + * @return QueueInterface + */ + public function getQueue() + { + return $this->queue; + } +} diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index c3331868b273b..6cceb47ed6883 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -10,7 +10,7 @@ "magento/module-authorization": "100.3.*", "magento/module-backend": "101.0.*", "magento/module-ui": "101.1.*", - "php": "~7.1.3||~7.2.0" + "php": "~7.1.3||~7.2.0||~7.3.0" }, "suggest": { "magento/module-admin-notification": "100.3.*", @@ -29,5 +29,5 @@ "Magento\\AsynchronousOperations\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index b904e69867439..c5ed74814cfb9 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*" }, @@ -22,5 +22,5 @@ "Magento\\Authorization\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Authorizenet/composer.json b/app/code/Magento/Authorizenet/composer.json index c0c06a97fc058..367d1566d3c64 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -31,5 +31,5 @@ "Magento\\Authorizenet\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml index fc86c0d2dc68d..3f2037f70b2df 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml @@ -85,9 +85,11 @@ </field> <field id="min_order_total" translate="label" type="text" sortOrder="190" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Minimum Order Total</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="max_order_total" translate="label" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Maximum Order Total</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="sort_order" translate="label" type="text" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml index 2ef93aca480c7..3088713989453 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml +++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/iframe.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var $block \Magento\Authorizenet\Block\Transparent\Iframe */ @@ -15,15 +13,15 @@ $helper = $block->getHelper('adminhtml'); <html> <head> <script> - <?php if (isset($params['redirect'])): ?> + <?php if (isset($params['redirect'])) : ?> window.location="<?= $block->escapeUrl($params['redirect']) ?>"; <?php endif; ?> - <?php if (isset($params['redirect_parent'])): ?> + <?php if (isset($params['redirect_parent'])) : ?> window.top.location="<?= $block->escapeUrl($params['redirect_parent']) ?>"; <?php endif; ?> - <?php if (isset($params['error_msg'])): ?> + <?php if (isset($params['error_msg'])) : ?> window.top.directPostModel.showError(<?= /* @noEscape */ json_encode((array)$params['error_msg']) ?>); - <?php if (isset($params['x_invoice_num'])): ?> + <?php if (isset($params['x_invoice_num'])) : ?> window.top.directPostModel.successUrl="<?= $block->escapeUrl($helper->getSuccessOrderUrl($params)) ?>"; <?php endif; ?> <?php endif; ?> diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml index 95040255f13c6..bec87738a83c1 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml +++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** * @var \Magento\Authorizenet\Block\Transparent\Iframe $block * @see \Magento\Authorizenet\Block\Transparent\Iframe @@ -12,7 +11,7 @@ $code = $block->escapeHtml($block->getMethodCode()); $method = $block->getMethod(); $controller = $block->escapeHtml($block->getRequest()->getControllerName()); -$orderUrl = $block->escapeUrl($this->helper('Magento\Authorizenet\Helper\Backend\Data')->getPlaceOrderAdminUrl()); +$orderUrl = $block->escapeUrl($block->getHelper('adminhtml')->getPlaceOrderAdminUrl()); $ccType = $block->getInfoData('cc_type'); $ccExpMonth = $block->getInfoData('cc_exp_month'); $ccExpYear = $block->getInfoData('cc_exp_year'); @@ -41,9 +40,9 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); 'validate-cc-type-select':'#<?= /* @noEscape */ $code ?>_cc_number' }"> <option value=""><?= $block->escapeHtml(__('Please Select')) ?></option> - <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName): ?> + <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName) : ?> <option value="<?= $block->escapeHtml($typeCode) ?>" - <?php if ($typeCode == $ccType): ?>selected="selected"<?php endif; ?>> + <?php if ($typeCode == $ccType) : ?>selected="selected"<?php endif; ?>> <?= $block->escapeHtml($typeName) ?> </option> <?php endforeach; ?> @@ -81,9 +80,9 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); 'required':true, 'validate-cc-exp':'#<?= /* @noEscape */ $code ?>_expiration_yr' }"> - <?php foreach ($block->getCcMonths() as $k => $v): ?> + <?php foreach ($block->getCcMonths() as $k => $v) : ?> <option value="<?= $block->escapeHtml($k) ?>" - <?php if ($k == $ccExpMonth): ?>selected="selected"<?php endif; ?>> + <?php if ($k == $ccExpMonth) : ?>selected="selected"<?php endif; ?>> <?= $block->escapeHtml($v) ?> </option> <?php endforeach; ?> @@ -93,9 +92,9 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); class="admin__control-select admin__control-select-year" data-container="<?= /* @noEscape */ $code ?>-cc-year" data-validate="{required:true}"> - <?php foreach ($block->getCcYears() as $k => $v): ?> + <?php foreach ($block->getCcYears() as $k => $v) : ?> <option value="<?= /* @noEscape */ $k ? $block->escapeHtml($k) : '' ?>" - <?php if ($k == $ccExpYear): ?>selected="selected"<?php endif; ?>> + <?php if ($k == $ccExpYear) : ?>selected="selected"<?php endif; ?>> <?= $block->escapeHtml($v) ?> </option> <?php endforeach; ?> @@ -103,7 +102,7 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </div> </div> - <?php if ($block->hasVerification()): ?> + <?php if ($block->hasVerification()) : ?> <div class="admin__field _required field-cvv"> <label class="admin__field-label" for="<?= /* @noEscape */ $code ?>_cc_cid" diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml index ac91fa30bfbe0..15325e15de1e1 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml +++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** * @var \Magento\Authorizenet\Block\Adminhtml\Order\View\Info\FraudDetails $block */ @@ -12,41 +11,41 @@ $payment = $block->getPayment(); $fraudDetails = $payment->getAdditionalInformation('fraud_details'); ?> -<?php if (!empty($fraudDetails)): ?> +<?php if (!empty($fraudDetails)) : ?> <div class="admin__page-section-item-title"> <span class="title"><?= $block->escapeHtml(__('Fraud Detection ')) ?></span> </div> <div class="admin__page-section-item-content"> <div class="order-payment-additional"> - <?php if(!empty($fraudDetails['fds_filter_action'])): ?> + <?php if (!empty($fraudDetails['fds_filter_action'])) : ?> <?= $block->escapeHtml(__('FDS Filter Action')) ?>: <?= $block->escapeHtml($fraudDetails['fds_filter_action']) ?> </br> <?php endif; ?> - <?php if(!empty($fraudDetails['avs_response'])): ?> + <?php if (!empty($fraudDetails['avs_response'])) : ?> <?= $block->escapeHtml(__('AVS Response')) ?>: <?= $block->escapeHtml($fraudDetails['avs_response']) ?> </br> <?php endif; ?> - <?php if(!empty($fraudDetails['card_code_response'])): ?> + <?php if (!empty($fraudDetails['card_code_response'])) : ?> <?= $block->escapeHtml(__('Card Code Response')) ?>: <?= $block->escapeHtml($fraudDetails['card_code_response']) ?> </br> <?php endif; ?> - <?php if(!empty($fraudDetails['cavv_response']) || ($fraudDetails['cavv_response'] === 0)): ?> + <?php if (!empty($fraudDetails['cavv_response']) || ($fraudDetails['cavv_response'] === 0)) : ?> <?= $block->escapeHtml(__('CAVV Response')) ?>: <?= $block->escapeHtml($fraudDetails['cavv_response']) ?> </br> <?php endif; ?> - <?php if(!empty($fraudDetails['fraud_filters'])): ?> + <?php if (!empty($fraudDetails['fraud_filters'])) : ?> <strong><?= $block->escapeHtml(__('Fraud Filters')) ?>: </strong></br> - <?php foreach($fraudDetails['fraud_filters'] as $filter): ?> + <?php foreach ($fraudDetails['fraud_filters'] as $filter) : ?> <?= $block->escapeHtml($filter['name']) ?>: <?= $block->escapeHtml($filter['action']) ?> </br> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StubDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StubDataBuilder.php new file mode 100644 index 0000000000000..794c120f94451 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StubDataBuilder.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Request; + +use Magento\Payment\Gateway\Request\BuilderInterface; + +/** + * Stub data builder. + * + * Since the order of params is matters for Authorize.net request, + * this builder is used to reserve a place in builders sequence. + */ +class StubDataBuilder implements BuilderInterface +{ + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + return []; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php index f0dff200e802b..acbde62bacd77 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php @@ -22,12 +22,19 @@ class CloseTransactionHandler implements HandlerInterface */ private $subjectReader; + /** + * @var bool + */ + private $closeTransaction; + /** * @param SubjectReader $subjectReader + * @param bool $closeTransaction */ - public function __construct(SubjectReader $subjectReader) + public function __construct(SubjectReader $subjectReader, bool $closeTransaction = true) { $this->subjectReader = $subjectReader; + $this->closeTransaction = $closeTransaction; } /** @@ -39,7 +46,7 @@ public function handle(array $handlingSubject, array $response): void $payment = $paymentDO->getPayment(); if ($payment instanceof Payment) { - $payment->setIsTransactionClosed(true); + $payment->setIsTransactionClosed($this->closeTransaction); $payment->setShouldCloseParentTransaction(true); } } diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml index e9a194435e3eb..eac4affcb9db6 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml @@ -7,40 +7,43 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="ConfigureAuthorizenetAcceptjs"> + <actionGroup name="ConfigureAuthorizenetAcceptjs" extends="EnableAuthorizenetAcceptjs"> <arguments> <argument name="paymentAction" type="string"/> </arguments> - <!-- Navigate to configuration --> - <waitForPageLoad stepKey="waitForStores"/> - <click stepKey="clickOnStores" selector="{{AdminMenuSection.stores}}"/> - <waitForPageLoad stepKey="waitForConfiguration"/> - <click stepKey="clickOnConfiguration" selector="{{StoresSubmenuSection.configuration}}"/> - <waitForPageLoad stepKey="waitForSales"/> - <waitForElementVisible stepKey="waitForVisibleHack" selector="{{AdminMenuSection.currencySetup}}"/> - <scrollTo stepKey="scrollToSales" selector="{{StoresConfigurationListSection.sales}}"/> - <click stepKey="clickOnSales" selector="{{StoresConfigurationListSection.sales}}" /> - <waitForPageLoad stepKey="waitForPaymentMethods"/> - <click stepKey="clickOnPaymentMethods" selector="{{StoresConfigurationListSection.salesPaymentMethods}}" /> - <waitForPageLoad stepKey="waitForOpenConfiguration"/> - <scrollTo stepKey="scrollToOpenConfig" selector="{{AuthorizenetAcceptjsConfigurationSection.openSectionToggle}}"/> - <conditionalClick stepKey="openConfiguration" selector="{{AuthorizenetAcceptjsConfigurationSection.openSectionToggle}}" dependentSelector="{{AuthorizenetAcceptjsConfigurationSection.alreadyOpenSectionToggle}}" visible="false"/> - <!-- Fill Auth.net fields and save --> - <waitForPageLoad stepKey="waitToFillApiLogin"/> + <waitForElementVisible selector="{{AuthorizenetAcceptjsConfigurationSection.paymentActionCheckbox}}" stepKey="waitForFormVisible"/> <conditionalClick selector="{{AuthorizenetAcceptjsConfigurationSection.paymentActionCheckbox}}" stepKey="uncheckPaymentActionDefault" dependentSelector="{{AuthorizenetAcceptjsConfigurationSection.paymentActionSelectDisabled}}" visible="true"/> <selectOption selector="{{AuthorizenetAcceptjsConfigurationSection.paymentActionSelect}}" stepKey="selectPaymentAction" userInput="{{paymentAction}}"/> - <scrollTo stepKey="scrollToApiLoginId" selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}}"/> - <fillField stepKey="fillApiLoginId" selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}}" userInput="{{_CREDS.authorizenet_acceptjs_api_login_id}}"/> - <fillField stepKey="fillTransactionKey" selector="{{AuthorizenetAcceptjsConfigurationSection.transactionKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_transaction_key}}"/> - <fillField stepKey="fillPublicClientKey" selector="{{AuthorizenetAcceptjsConfigurationSection.publicClientKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_public_client_key}}"/> - <fillField stepKey="fillSignatureKey" selector="{{AuthorizenetAcceptjsConfigurationSection.signatureKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_signature_key}}"/> - <uncheckOption stepKey="uncheckCheckbox" selector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultCheckbox}}"/> - <selectOption stepKey="fillExpYear" selector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultSelect}}" userInput="Yes"/> - <click stepKey="clickOnSave" selector="{{ConfigurationMainActionsSection.save}}" /> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}}" stepKey="scrollToApiLoginId"/> + <fillField selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}}" userInput="{{_CREDS.authorizenet_acceptjs_api_login_id}}" stepKey="fillApiLoginId"/> + <fillField selector="{{AuthorizenetAcceptjsConfigurationSection.transactionKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_transaction_key}}" stepKey="fillTransactionKey"/> + <fillField selector="{{AuthorizenetAcceptjsConfigurationSection.publicClientKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_public_client_key}}" stepKey="fillPublicClientKey"/> + <fillField selector="{{AuthorizenetAcceptjsConfigurationSection.signatureKeyField}}" userInput="{{_CREDS.authorizenet_acceptjs_signature_key}}" stepKey="fillSignatureKey"/> </actionGroup> <actionGroup name="DisableAuthorizenetAcceptjs"> <magentoCLI stepKey="disableAuthorizenetAcceptjs" command="config:set payment/authorizenet_acceptjs/active 0"/> </actionGroup> + + <actionGroup name="EnableAuthorizenetAcceptjs"> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.openSectionToggle}}" stepKey="scrollToAuthorizeNetConfigSection"/> + <conditionalClick selector="{{AuthorizenetAcceptjsConfigurationSection.openSectionToggle}}" dependentSelector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultSelect}}" visible="false" stepKey="openConfigSection"/> + <waitForElementVisible selector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultSelect}}" stepKey="waitForEnableFieldVisible"/> + <uncheckOption selector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultCheckbox}}" stepKey="uncheckCheckbox"/> + <selectOption selector="{{AuthorizenetAcceptjsConfigurationSection.enabledDefaultSelect}}" userInput="Yes" stepKey="enablePayment"/> + </actionGroup> + + <actionGroup name="AssertAuthorizenetAcceptjsRequiredFieldsValidationIsPresentOnSave"> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}}" stepKey="scrollToApiLoginIdField"/> + <see selector="{{AuthorizenetAcceptjsConfigurationSection.apiLoginIdField}} + {{AdminConfigSection.fieldError}}" userInput="This is a required field." stepKey="seeApiLoginIdRequiredMessage"/> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.publicClientKeyField}}" stepKey="scrollToPublicClientKeyField"/> + <see selector="{{AuthorizenetAcceptjsConfigurationSection.publicClientKeyField}} + {{AdminConfigSection.fieldError}}" userInput="This is a required field." stepKey="seePublicClientKeyRequiredErrorMessage"/> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.transactionKeyField}}" stepKey="scrollTransactionKeyField"/> + <see selector="{{AuthorizenetAcceptjsConfigurationSection.transactionKeyField}} + {{AdminConfigSection.fieldError}}" userInput="This is a required field." stepKey="seeTransactionKeyRequiredErrorMessage"/> + <scrollTo selector="{{AuthorizenetAcceptjsConfigurationSection.signatureKeyField}}" stepKey="scrollToSignatureKeyField"/> + <see selector="{{AuthorizenetAcceptjsConfigurationSection.signatureKeyField}} + {{AdminConfigSection.fieldError}}" userInput="This is a required field." stepKey="seeSignatureKeyRequiredErrorMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/ConfigureAuthorizenetAcceptjsWithoutRequiredOptionsTest.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/ConfigureAuthorizenetAcceptjsWithoutRequiredOptionsTest.xml new file mode 100644 index 0000000000000..cbb702c26f17d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/ConfigureAuthorizenetAcceptjsWithoutRequiredOptionsTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ConfigureAuthorizenetAcceptjsWithoutRequiredOptionsTest"> + <annotations> + <stories value="Authorize.net Accept.js"/> + <title value="Unable to configure Authorize.net Accept.js without required options"/> + <description value="Unable to configure Authorize.net Accept.js without required options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-17805"/> + <useCaseId value="MC-17753"/> + <group value="AuthorizenetAcceptjs"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <actionGroup ref="EnableAuthorizenetAcceptjs" stepKey="enableAuthorizenetAcceptjs"/> + <actionGroup ref="AssertAuthorizenetAcceptjsRequiredFieldsValidationIsPresentOnSave" stepKey="assertErrorMessages"/> + </test> +</tests> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml index 42a78291436ed..7f25482d627e1 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml @@ -31,9 +31,11 @@ </createData> <!--Configure Auth.net--> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> <actionGroup ref="ConfigureAuthorizenetAcceptjs" stepKey="configureAuthorizenetAcceptjs"> <argument name="paymentAction" value="Authorize Only"/> </actionGroup> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig"/> </before> <after> @@ -54,6 +56,7 @@ <waitForPageLoad stepKey="waitForProductPage"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForCartToFill"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!--Checkout steps--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml index 95c2436905212..919c32d8f70d6 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml @@ -36,9 +36,11 @@ <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> <!--Configure Auth.net--> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> <actionGroup ref="ConfigureAuthorizenetAcceptjs" stepKey="configureAuthorizenetAcceptjs"> <argument name="paymentAction" value="Authorize and Capture"/> </actionGroup> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig"/> </before> <after> @@ -55,8 +57,10 @@ <waitForPageLoad stepKey="waitForProductPage"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForCartToFill"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCartAgain"/> <waitForPageLoad stepKey="waitForCartToFillAgain"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> <!--Checkout steps--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php index ebb95263f54d2..bd439a336786b 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php @@ -17,6 +17,9 @@ use Magento\Quote\Api\Data\PaymentInterface; use PHPUnit\Framework\TestCase; +/** + * Tests DataAssignObserver + */ class DataAssignObserverTest extends TestCase { public function testExecuteSetsProperData() @@ -30,9 +33,7 @@ public function testExecuteSetsProperData() $observerContainer = $this->createMock(Observer::class); $event = $this->createMock(Event::class); $paymentInfoModel = $this->createMock(InfoInterface::class); - $dataObject = new DataObject([ - PaymentInterface::KEY_ADDITIONAL_DATA => $additionalInfo - ]); + $dataObject = new DataObject([PaymentInterface::KEY_ADDITIONAL_DATA => $additionalInfo]); $observerContainer->method('getEvent') ->willReturn($event); $event->method('getDataByKey') diff --git a/app/code/Magento/AuthorizenetAcceptjs/composer.json b/app/code/Magento/AuthorizenetAcceptjs/composer.json index d1ae34726bcac..8c5a18bbe6ec8 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/composer.json +++ b/app/code/Magento/AuthorizenetAcceptjs/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-payment": "100.3.*", "magento/module-sales": "102.0.*", @@ -28,5 +28,5 @@ "Magento\\AuthorizenetAcceptjs\\": "" } }, - "version": "100.3.1" + "version": "100.3.2" } diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml index 320f8f79ee28a..f4059aebbe3e3 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml @@ -11,4 +11,29 @@ <argument name="config" xsi:type="object">Magento\AuthorizenetAcceptjs\Model\Ui\ConfigProvider</argument> </arguments> </type> + <virtualType name="AuthorizenetAcceptjsAuthorizeRequest" type="Magento\Payment\Gateway\Request\BuilderComposite"> + <arguments> + <argument name="builders" xsi:type="array"> + <item name="request_type" xsi:type="string">AuthorizenetAcceptjsTransactionRequestTypeBuilder</item> + <item name="store" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder</item> + <item name="transaction_type" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\AuthorizeDataBuilder</item> + <item name="amount" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\AmountDataBuilder</item> + <item name="payment" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\PaymentDataBuilder</item> + <item name="shipping" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\ShippingDataBuilder</item> + <item name="solution" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\SolutionDataBuilder</item> + <item name="order" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\OrderDataBuilder</item> + <item name="po" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\PoDataBuilder</item> + <item name="customer" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\CustomerDataBuilder</item> + <item name="address" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\AddressDataBuilder</item> + <item name="custom_settings" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\CustomSettingsBuilder</item> + <item name="passthrough_data" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder</item> + </argument> + </arguments> + </virtualType> + <virtualType name="AuthorizenetAcceptjsAuthorizeCommand" type="Magento\Payment\Gateway\Command\GatewayCommand"> + <arguments> + <argument name="validator" xsi:type="object">AuthorizenetAcceptjsTransactionValidator</argument> + </arguments> + </virtualType> </config> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml index 279a904d916a2..8623919cf5d6b 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml @@ -17,7 +17,7 @@ <group id="authorizenet_acceptjs_required"/> </requires> </field> - <group id="required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="5"> + <group id="required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="15"> <label>Basic Authorize.Net Settings</label> <attribute type="expanded">1</attribute> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> @@ -39,25 +39,44 @@ <label>API Login ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <config_path>payment/authorizenet_acceptjs/login</config_path> + <validate>required-entry</validate> + <depends> + <field id="*/authorizenet_acceptjs/active">1</field> + </depends> </field> <field id="trans_key" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Transaction Key</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <config_path>payment/authorizenet_acceptjs/trans_key</config_path> + <validate>required-entry</validate> + <depends> + <field id="*/authorizenet_acceptjs/active">1</field> + </depends> </field> <field id="public_client_key" translate="label" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Public Client Key</label> <config_path>payment/authorizenet_acceptjs/public_client_key</config_path> + <validate>required-entry</validate> + <depends> + <field id="*/authorizenet_acceptjs/active">1</field> + </depends> </field> <field id="trans_signature_key" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Signature Key</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <config_path>payment/authorizenet_acceptjs/trans_signature_key</config_path> + <validate>required-entry</validate> + <depends> + <field id="*/authorizenet_acceptjs/active">1</field> + </depends> </field> <field id="trans_md5" translate="label" type="obscure" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Merchant MD5 (deprecated)</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <config_path>payment/authorizenet_acceptjs/trans_md5</config_path> + <depends> + <field id="*/authorizenet_acceptjs/active">1</field> + </depends> </field> </group> <group id="advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="20"> @@ -101,10 +120,12 @@ <field id="min_order_total" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Minimum Order Total</label> <config_path>payment/authorizenet_acceptjs/min_order_total</config_path> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="max_order_total" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Maximum Order Total</label> <config_path>payment/authorizenet_acceptjs/max_order_total</config_path> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml index cf10557d3869a..145bcf22fd912 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml @@ -134,6 +134,7 @@ <arguments> <argument name="requestBuilder" xsi:type="object">AuthorizenetAcceptjsRefundRequest</argument> <argument name="handler" xsi:type="object">AuthorizenetAcceptjsRefundSettledHandler</argument> + <argument name="validator" xsi:type="object">AuthorizenetAcceptjsTransactionValidator</argument> </arguments> </virtualType> <virtualType name="AuthorizenetAcceptjsCaptureCommand" type="Magento\AuthorizenetAcceptjs\Gateway\Command\CaptureStrategyCommand"> @@ -145,6 +146,7 @@ <arguments> <argument name="requestBuilder" xsi:type="object">AuthorizenetAcceptjsCaptureRequest</argument> <argument name="handler" xsi:type="object">AuthorizenetAcceptjsCaptureTransactionHandler</argument> + <argument name="validator" xsi:type="object">AuthorizenetAcceptjsTransactionValidator</argument> </arguments> </virtualType> <virtualType name="AuthorizenetAcceptjsVoidCommand" type="Magento\Payment\Gateway\Command\GatewayCommand"> @@ -188,10 +190,15 @@ </argument> </arguments> </virtualType> + <virtualType name="CloseCaptureTransactionHandler" type="Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler"> + <arguments> + <argument name="closeTransaction" xsi:type="boolean">false</argument> + </arguments> + </virtualType> <virtualType name="AuthorizenetAcceptjsCaptureTransactionHandler" type="Magento\Payment\Gateway\Response\HandlerChain"> <arguments> <argument name="handlers" xsi:type="array"> - <item name="close_parent_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler</item> + <item name="close_transaction" xsi:type="string">CloseCaptureTransactionHandler</item> </argument> </arguments> </virtualType> @@ -258,6 +265,7 @@ <item name="po" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\PoDataBuilder</item> <item name="customer" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\CustomerDataBuilder</item> <item name="address" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\AddressDataBuilder</item> + <item name="3d_secure" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\StubDataBuilder</item> <item name="custom_settings" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\CustomSettingsBuilder</item> <item name="passthrough_data" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder</item> </argument> diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml index 045bd5cfd81b2..b757e55aaddee 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** * @var Magento\AuthorizenetAcceptjs\Block\Form $block */ @@ -23,8 +22,8 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); <select id="<?= /* @noEscape */ $code ?>_cc_type" name="payment[cc_type]" class="required-entry validate-cc-type-select admin__control-select"> <option value=""></option> - <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName): ?> - <option value="<?= $block->escapeHtml($typeCode) ?>" <?php if ($typeCode == $ccType): ?>selected="selected"<?php endif ?>> + <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName) : ?> + <option value="<?= $block->escapeHtml($typeCode) ?>" <?php if ($typeCode == $ccType) : ?>selected="selected"<?php endif ?>> <?= $block->escapeHtml($typeName) ?> </option> <?php endforeach ?> @@ -48,18 +47,18 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); <div class="admin__field-control"> <select id="<?= /* @noEscape */ $code ?>_cc_exp_month" name="payment[cc_exp_month]" class="admin__control-select admin__control-select-month validate-cc-exp required-entry"> - <?php foreach ($block->getCcMonths() as $k => $v): ?> + <?php foreach ($block->getCcMonths() as $k => $v) : ?> <option value="<?= $block->escapeHtml($k) ?>" - <?php if ($k == $ccExpMonth): ?>selected="selected"<?php endif ?>> + <?php if ($k == $ccExpMonth) : ?>selected="selected"<?php endif ?>> <?= $block->escapeHtml($v) ?> </option> <?php endforeach; ?> </select> <select id="<?= /* @noEscape */ $code ?>_cc_exp_year" name="payment[cc_exp_year]" class="admin__control-select admin__control-select-year required-entry"> - <?php foreach ($block->getCcYears() as $k => $v): ?> + <?php foreach ($block->getCcYears() as $k => $v) : ?> <option value="<?= /* @noEscape */ $k ? $block->escapeHtml($k) : '' ?>" - <?php if ($k == $ccExpYear): ?>selected="selected"<?php endif ?>> + <?php if ($k == $ccExpYear) : ?>selected="selected"<?php endif ?>> <?= $block->escapeHtml($v) ?> </option> <?php endforeach ?> @@ -67,7 +66,7 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </div> </div> - <?php if ($block->isCvvEnabled()): ?> + <?php if ($block->isCvvEnabled()) : ?> <div class="field-number required admin__field _required"> <label class="admin__field-label" for="<?= /* @noEscape */ $code ?>_cc_cid"> <span><?= $block->escapeHtml(__('Card Verification Number')) ?></span> diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml index 2989f99c0462d..6be6008dba507 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml @@ -9,12 +9,16 @@ <script> //<![CDATA[ require( - ['Magento_AuthorizenetAcceptjs/js/payment-form'], - function(Authorizenet) { + [ + 'Magento_AuthorizenetAcceptjs/js/authorizenet', + 'jquery', + 'domReady!' + ], function(AuthorizenetAcceptjs, $) { var config = <?= /* @noEscape */ $block->getPaymentConfig() ?>, - form = "#payment_form_<?= $block->escapeJs($block->escapeHtml($block->getMethodCode())) ?>"; + form = $('#payment_form_<?= /* @noEscape */ $block->escapeJs($block->escapeHtml($block->getMethodCode())) ?>'); - new Authorizenet(config, form); + config.active = form.length > 0 && !form.is(':hidden'); + new AuthorizenetAcceptjs(config); }); //]]> </script> diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/method-renderer/authorizenet-accept.js b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/method-renderer/authorizenet-accept.js index 983318c4cdaaf..bba1290a9eedd 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/method-renderer/authorizenet-accept.js +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/method-renderer/authorizenet-accept.js @@ -91,8 +91,10 @@ define([ return; } - authData.clientKey = window.checkoutConfig.payment[this.getCode()].clientKey; - authData.apiLoginID = window.checkoutConfig.payment[this.getCode()].apiLoginID; + authData.clientKey = window.checkoutConfig.payment[this.getCode()].clientKey !== null ? + window.checkoutConfig.payment[this.getCode()].clientKey : ''; + authData.apiLoginID = window.checkoutConfig.payment[this.getCode()].apiLoginID !== null ? + window.checkoutConfig.payment[this.getCode()].apiLoginID : ''; cardData.cardNumber = this.creditCardNumber(); cardData.month = this.creditCardExpMonth(); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/template/payment/authorizenet-acceptjs.html b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/template/payment/authorizenet-acceptjs.html index 6db52a2b1025e..1e41c2b49adba 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/template/payment/authorizenet-acceptjs.html +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/template/payment/authorizenet-acceptjs.html @@ -35,6 +35,7 @@ <button class="action primary checkout" type="submit" click="beforePlaceOrder" + css="disabled: !isPlaceOrderActionAllowed()" attr="title: $t('Place Order')" > <span translate="'Place Order'"></span> diff --git a/app/code/Magento/AuthorizenetCardinal/Gateway/Request/Authorize3DSecureBuilder.php b/app/code/Magento/AuthorizenetCardinal/Gateway/Request/Authorize3DSecureBuilder.php new file mode 100644 index 0000000000000..00def8ce2b0cf --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Gateway/Request/Authorize3DSecureBuilder.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetCardinal\Model\Config; +use Magento\CardinalCommerce\Model\Response\JwtParserInterface; +use Magento\Payment\Gateway\Request\BuilderInterface; +use Magento\Sales\Model\Order\Payment; + +/** + * Adds the cardholder authentication information to the request + */ +class Authorize3DSecureBuilder implements BuilderInterface +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @var Config + */ + private $config; + + /** + * @var JwtParserInterface + */ + private $jwtParser; + + /** + * @param SubjectReader $subjectReader + * @param Config $config + * @param JwtParserInterface $jwtParser + */ + public function __construct( + SubjectReader $subjectReader, + Config $config, + JwtParserInterface $jwtParser + ) { + $this->subjectReader = $subjectReader; + $this->config = $config; + $this->jwtParser = $jwtParser; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + if ($this->config->isActive() === false) { + return []; + } + + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $cardinalJwt = (string)$payment->getAdditionalInformation('cardinalJWT'); + $jwtPayload = $this->jwtParser->execute($cardinalJwt); + $eciFlag = $jwtPayload['Payload']['Payment']['ExtendedData']['ECIFlag'] ?? ''; + $cavv = $jwtPayload['Payload']['Payment']['ExtendedData']['CAVV'] ?? ''; + $data = [ + 'transactionRequest' => [ + 'cardholderAuthentication' => [ + 'authenticationIndicator' => $eciFlag, + 'cardholderAuthenticationValue' => $cavv + ], + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/Gateway/Validator/CavvResponseValidator.php b/app/code/Magento/AuthorizenetCardinal/Gateway/Validator/CavvResponseValidator.php new file mode 100644 index 0000000000000..036c1fa332ebf --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Gateway/Validator/CavvResponseValidator.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Gateway\Validator; + +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetCardinal\Model\Config; +use Magento\Payment\Gateway\Validator\AbstractValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; + +/** + * Validates cardholder authentication verification response code. + */ +class CavvResponseValidator extends AbstractValidator +{ + /** + * The result code that authorize.net returns if CAVV passed validation. + */ + private const RESULT_CODE_SUCCESS = '2'; + + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @var ResultInterfaceFactory + */ + private $resultFactory; + + /** + * @var Config + */ + private $config; + + /** + * @param ResultInterfaceFactory $resultFactory + * @param SubjectReader $subjectReader + * @param Config $config + */ + public function __construct( + ResultInterfaceFactory $resultFactory, + SubjectReader $subjectReader, + Config $config + ) { + parent::__construct($resultFactory); + + $this->resultFactory = $resultFactory; + $this->subjectReader = $subjectReader; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function validate(array $validationSubject): ResultInterface + { + if ($this->config->isActive() === false) { + return $this->createResult(true); + } + + $response = $this->subjectReader->readResponse($validationSubject); + $transactionResponse = $response['transactionResponse']; + + $cavvResultCode = $transactionResponse['cavvResultCode'] ?? ''; + $isValid = $cavvResultCode === self::RESULT_CODE_SUCCESS; + $errorCodes = []; + $errorMessages = []; + + if (!$isValid) { + $errorCodes[] = $transactionResponse['cavvResultCode']; + $errorMessages[] = 'CAVV failed validation'; + } + + return $this->createResult($isValid, $errorMessages, $errorCodes); + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/LICENSE.txt b/app/code/Magento/AuthorizenetCardinal/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/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 <insert your license name here>" 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/AuthorizenetCardinal/LICENSE_AFL.txt b/app/code/Magento/AuthorizenetCardinal/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/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 <insert your license name here>" 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/AuthorizenetCardinal/Model/Checkout/ConfigProvider.php b/app/code/Magento/AuthorizenetCardinal/Model/Checkout/ConfigProvider.php new file mode 100644 index 0000000000000..d0cde9c643ebf --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Model/Checkout/ConfigProvider.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Model\Checkout; + +use Magento\AuthorizenetCardinal\Model\Config; +use Magento\Checkout\Model\ConfigProviderInterface; + +/** + * Configuration provider. + */ +class ConfigProvider implements ConfigProviderInterface +{ + /** + * @var Config + */ + private $config; + + /** + * @param Config $config + */ + public function __construct( + Config $config + ) { + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getConfig(): array + { + $config['cardinal'] = [ + 'isActiveFor' => [ + 'authorizenet' => $this->config->isActive() + ] + ]; + + return $config; + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/Model/Config.php b/app/code/Magento/AuthorizenetCardinal/Model/Config.php new file mode 100644 index 0000000000000..e70a6a2e39c1f --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Model/Config.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * AuthorizenetCardinal integration configuration. + * + * Class is a proxy service for retrieving configuration settings. + */ +class Config +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * If this config option set to false no AuthorizenetCardinal integration should be available + * + * @param int|null $storeId + * @return bool + */ + public function isActive(?int $storeId = null): bool + { + $enabled = $this->scopeConfig->isSetFlag( + 'three_d_secure/cardinal/enabled_authorizenet', + ScopeInterface::SCOPE_STORE, + $storeId + ); + + return $enabled; + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/Observer/DataAssignObserver.php b/app/code/Magento/AuthorizenetCardinal/Observer/DataAssignObserver.php new file mode 100644 index 0000000000000..cb2cdf64ae389 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Observer/DataAssignObserver.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Observer; + +use Magento\Framework\Event\Observer; +use Magento\Payment\Observer\AbstractDataAssignObserver; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\AuthorizenetCardinal\Model\Config; + +/** + * Adds the payment info to the payment object + */ +class DataAssignObserver extends AbstractDataAssignObserver +{ + /** + * JWT key + */ + private const JWT_KEY = 'cardinalJWT'; + + /** + * @var Config + */ + private $config; + + /** + * @param Config $config + */ + public function __construct( + Config $config + ) { + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function execute(Observer $observer) + { + if ($this->config->isActive() === false) { + return; + } + + $data = $this->readDataArgument($observer); + $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); + if (!is_array($additionalData)) { + return; + } + + $paymentInfo = $this->readPaymentModelArgument($observer); + if (isset($additionalData[self::JWT_KEY])) { + $paymentInfo->setAdditionalInformation( + self::JWT_KEY, + $additionalData[self::JWT_KEY] + ); + } + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/README.md b/app/code/Magento/AuthorizenetCardinal/README.md new file mode 100644 index 0000000000000..2324f680bafc9 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/README.md @@ -0,0 +1 @@ +The AuthorizenetCardinal module provides a possibility to enable 3-D Secure 2.0 support for AuthorizenetAcceptjs payment integration. \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetCardinal/Test/Unit/Observer/DataAssignObserverTest.php b/app/code/Magento/AuthorizenetCardinal/Test/Unit/Observer/DataAssignObserverTest.php new file mode 100644 index 0000000000000..9f560507e34db --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/Test/Unit/Observer/DataAssignObserverTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetCardinal\Test\Unit\Observer; + +use Magento\AuthorizenetCardinal\Model\Config; +use Magento\Framework\DataObject; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Payment\Model\InfoInterface; +use Magento\Payment\Observer\AbstractDataAssignObserver; +use Magento\AuthorizenetCardinal\Observer\DataAssignObserver; +use Magento\Quote\Api\Data\PaymentInterface; +use PHPUnit\Framework\TestCase; + +/** + * Class DataAssignObserverTest + */ +class DataAssignObserverTest extends TestCase +{ + /** + * Tests setting JWT in payment additional information. + */ + public function testExecuteSetsProperData() + { + $additionalInfo = [ + 'cardinalJWT' => 'foo' + ]; + + $config = $this->createMock(Config::class); + $config->method('isActive') + ->willReturn(true); + $observerContainer = $this->createMock(Observer::class); + $event = $this->createMock(Event::class); + $paymentInfoModel = $this->createMock(InfoInterface::class); + $dataObject = new DataObject([PaymentInterface::KEY_ADDITIONAL_DATA => $additionalInfo]); + $observerContainer->method('getEvent') + ->willReturn($event); + $event->method('getDataByKey') + ->willReturnMap( + [ + [AbstractDataAssignObserver::MODEL_CODE, $paymentInfoModel], + [AbstractDataAssignObserver::DATA_CODE, $dataObject] + ] + ); + $paymentInfoModel->expects($this->once()) + ->method('setAdditionalInformation') + ->with('cardinalJWT', 'foo'); + + $observer = new DataAssignObserver($config); + $observer->execute($observerContainer); + } + + /** + * Tests case when Cardinal JWT is absent. + */ + public function testDoesntSetDataWhenEmpty() + { + $config = $this->createMock(Config::class); + $config->method('isActive') + ->willReturn(true); + $observerContainer = $this->createMock(Observer::class); + $event = $this->createMock(Event::class); + $paymentInfoModel = $this->createMock(InfoInterface::class); + $observerContainer->method('getEvent') + ->willReturn($event); + $event->method('getDataByKey') + ->willReturnMap( + [ + [AbstractDataAssignObserver::MODEL_CODE, $paymentInfoModel], + [AbstractDataAssignObserver::DATA_CODE, new DataObject()] + ] + ); + $paymentInfoModel->expects($this->never()) + ->method('setAdditionalInformation'); + + $observer = new DataAssignObserver($config); + $observer->execute($observerContainer); + } + + /** + * Tests case when CardinalCommerce is disabled. + */ + public function testDoesntSetDataWhenDisabled() + { + $config = $this->createMock(Config::class); + $config->method('isActive') + ->willReturn(false); + $observerContainer = $this->createMock(Observer::class); + $observerContainer->expects($this->never()) + ->method('getEvent'); + $observer = new DataAssignObserver($config); + $observer->execute($observerContainer); + } +} diff --git a/app/code/Magento/AuthorizenetCardinal/composer.json b/app/code/Magento/AuthorizenetCardinal/composer.json new file mode 100644 index 0000000000000..00b017c420711 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/composer.json @@ -0,0 +1,32 @@ +{ + "name": "magento/module-authorizenet-cardinal", + "description": "Provides a possibility to enable 3-D Secure 2.0 support for Authorize.Net Acceptjs.", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-authorizenet-acceptjs": "100.3.*", + "magento/framework": "102.0.*", + "magento/module-cardinal-commerce": "100.3.*", + "magento/module-payment": "100.3.*", + "magento/module-sales": "102.0.*", + "magento/module-quote": "101.1.*", + "magento/module-checkout": "100.3.*", + "magento/module-store": "101.0.*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AuthorizenetCardinal\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml b/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..2be287a5e8743 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="three_d_secure"> + <group id="cardinal"> + <group id="config"> + <field id="enabled_authorize" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Enable for Authorize.Net</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <config_path>three_d_secure/cardinal/enabled_authorizenet</config_path> + </field> + </group> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/etc/config.xml b/app/code/Magento/AuthorizenetCardinal/etc/config.xml new file mode 100644 index 0000000000000..d94bcdc479008 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/config.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <three_d_secure> + <cardinal> + <enabled_authorizenet>0</enabled_authorizenet> + </cardinal> + </three_d_secure> + </default> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/etc/di.xml b/app/code/Magento/AuthorizenetCardinal/etc/di.xml new file mode 100644 index 0000000000000..568cb6f4cfc4c --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/di.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <virtualType name="AuthorizenetAcceptjsAuthorizeRequest"> + <arguments> + <argument name="builders" xsi:type="array"> + <item name="3d_secure" xsi:type="string">Magento\AuthorizenetCardinal\Gateway\Request\Authorize3DSecureBuilder</item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\AuthorizenetCardinal\Gateway\Validator\VirtualTransactionValidator" type="Magento\Payment\Gateway\Validator\ValidatorComposite"> + <arguments> + <argument name="chainBreakingValidators" xsi:type="array"> + <item name="general" xsi:type="boolean">true</item> + </argument> + <argument name="validators" xsi:type="array"> + <item name="general" xsi:type="string">AuthorizenetAcceptjsTransactionValidator</item> + <item name="cavv_response" xsi:type="string">Magento\AuthorizenetCardinal\Gateway\Validator\CavvResponseValidator</item> + </argument> + </arguments> + </virtualType> + <virtualType name="AuthorizenetAcceptjsAuthorizeCommand"> + <arguments> + <argument name="validator" xsi:type="object">Magento\AuthorizenetCardinal\Gateway\Validator\VirtualTransactionValidator</argument> + </arguments> + </virtualType> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/etc/events.xml b/app/code/Magento/AuthorizenetCardinal/etc/events.xml new file mode 100644 index 0000000000000..5b0afbe684699 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/events.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="payment_method_assign_data_authorizenet_acceptjs"> + <observer name="authorizenet_cardinal_data_assign" instance="Magento\AuthorizenetCardinal\Observer\DataAssignObserver" /> + </event> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/etc/frontend/di.xml b/app/code/Magento/AuthorizenetCardinal/etc/frontend/di.xml new file mode 100644 index 0000000000000..13c7a223e82d9 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/frontend/di.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Checkout\Model\CompositeConfigProvider"> + <arguments> + <argument name="configProviders" xsi:type="array"> + <item name="authorizenet_cardinal_config_provider" xsi:type="object"> + Magento\AuthorizenetCardinal\Model\Checkout\ConfigProvider + </item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/etc/module.xml b/app/code/Magento/AuthorizenetCardinal/etc/module.xml new file mode 100644 index 0000000000000..fdf8151311f43 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_AuthorizenetCardinal" > + <sequence> + <module name="Magento_AuthorizenetAcceptjs"/> + <module name="Magento_CardinalCommerce"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/AuthorizenetCardinal/registration.php b/app/code/Magento/AuthorizenetCardinal/registration.php new file mode 100644 index 0000000000000..0153e9eaa4d29 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AuthorizenetCardinal', __DIR__); diff --git a/app/code/Magento/AuthorizenetCardinal/view/frontend/requirejs-config.js b/app/code/Magento/AuthorizenetCardinal/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..81823cb2afc58 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/view/frontend/requirejs-config.js @@ -0,0 +1,15 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_AuthorizenetAcceptjs/js/view/payment/method-renderer/authorizenet-accept': { + 'Magento_AuthorizenetCardinal/js/authorizenet-accept-mixin': true + } + } + } +}; + diff --git a/app/code/Magento/AuthorizenetCardinal/view/frontend/web/js/authorizenet-accept-mixin.js b/app/code/Magento/AuthorizenetCardinal/view/frontend/web/js/authorizenet-accept-mixin.js new file mode 100644 index 0000000000000..20a917fc0f050 --- /dev/null +++ b/app/code/Magento/AuthorizenetCardinal/view/frontend/web/js/authorizenet-accept-mixin.js @@ -0,0 +1,72 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'Magento_CardinalCommerce/js/cardinal-client', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Ui/js/model/messageList' +], function ($, cardinalClient, fullScreenLoader, globalMessageList) { + 'use strict'; + + return function (originalComponent) { + return originalComponent.extend({ + defaults: { + cardinalJWT: null + }, + + /** + * Performs 3d-secure authentication + */ + beforePlaceOrder: function () { + var original = this._super.bind(this), + client = cardinalClient, + isActive = window.checkoutConfig.cardinal.isActiveFor.authorizenet, + cardData; + + if (!isActive || !$(this.formElement).valid()) { + return original(); + } + + cardData = { + accountNumber: this.creditCardNumber(), + expMonth: this.creditCardExpMonth(), + expYear: this.creditCardExpYear() + }; + + if (this.hasVerification()) { + cardData.cardCode = this.creditCardVerificationNumber(); + } + + fullScreenLoader.startLoader(); + client.startAuthentication(cardData) + .always(function () { + fullScreenLoader.stopLoader(); + }) + .done(function (jwt) { + this.cardinalJWT = jwt; + original(); + }.bind(this)) + .fail(function (errorMessage) { + globalMessageList.addErrorMessage({ + message: errorMessage + }); + }); + }, + + /** + * Adds cardinal response JWT to payment additional data. + * + * @returns {Object} + */ + getData: function () { + var originalData = this._super(); + + originalData['additional_data'].cardinalJWT = this.cardinalJWT; + + return originalData; + } + }); + }; +}); diff --git a/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php b/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php new file mode 100644 index 0000000000000..207d21994308f --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetGraphQl\Model; + +use Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderInterface; +use Magento\Framework\Stdlib\ArrayManager; + +/** + * SetPaymentMethod additional data provider model for Authorizenet payment method + */ +class AuthorizenetDataProvider implements AdditionalDataProviderInterface +{ + private const PATH_ADDITIONAL_DATA = 'authorizenet_acceptjs'; + + /** + * @var ArrayManager + */ + private $arrayManager; + + /** + * @param ArrayManager $arrayManager + */ + public function __construct( + ArrayManager $arrayManager + ) { + $this->arrayManager = $arrayManager; + } + + /** + * Return additional data + * + * @param array $data + * @return array + */ + public function getData(array $data): array + { + $additionalData = $this->arrayManager->get(static::PATH_ADDITIONAL_DATA, $data) ?? []; + foreach ($additionalData as $key => $value) { + $additionalData[$this->convertSnakeCaseToCamelCase($key)] = $value; + unset($additionalData[$key]); + } + return $additionalData; + } + + /** + * Convert an input string from snake_case to camelCase. + * + * @param string $input + * @return string + */ + private function convertSnakeCaseToCamelCase($input): string + { + return lcfirst(str_replace('_', '', ucwords($input, '_'))); + } +} diff --git a/app/code/Magento/AuthorizenetGraphQl/README.md b/app/code/Magento/AuthorizenetGraphQl/README.md new file mode 100644 index 0000000000000..8b920e569341f --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/README.md @@ -0,0 +1,3 @@ +# AuthorizenetGraphQl + + **AuthorizenetGraphQl** defines the data types needed to pass payment information data from the client to Magento. diff --git a/app/code/Magento/AuthorizenetGraphQl/composer.json b/app/code/Magento/AuthorizenetGraphQl/composer.json new file mode 100644 index 0000000000000..3c91e4bac5c2e --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-authorizenet-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "102.0.*", + "magento/module-quote-graph-ql": "100.3.*" + }, + "suggest": { + "magento/module-graph-ql": "100.3.*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AuthorizenetGraphQl\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/AuthorizenetGraphQl/etc/graphql/di.xml b/app/code/Magento/AuthorizenetGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..e8ea45091c044 --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/etc/graphql/di.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderPool"> + <arguments> + <argument name="dataProviders" xsi:type="array"> + <item name="authorizenet_acceptjs" xsi:type="object">Magento\AuthorizenetGraphQl\Model\AuthorizenetDataProvider</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/AuthorizenetGraphQl/etc/module.xml b/app/code/Magento/AuthorizenetGraphQl/etc/module.xml new file mode 100644 index 0000000000000..85a780a881975 --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_AuthorizenetGraphQl"/> +</config> diff --git a/app/code/Magento/AuthorizenetGraphQl/etc/schema.graphqls b/app/code/Magento/AuthorizenetGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..b6e817cc91d61 --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/etc/schema.graphqls @@ -0,0 +1,12 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +input PaymentMethodInput { + authorizenet_acceptjs: AuthorizenetInput @doc(description: "Defines the required attributes for Authorize.Net payments") +} + +input AuthorizenetInput { + opaque_data_descriptor: String! @doc(description: "Authorize.Net's description of the transaction request") + opaque_data_value: String! @doc(description: "The nonce returned by Authorize.Net") + cc_last_4: Int! @doc(description: "The last four digits of the credit or debit card") +} \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetGraphQl/registration.php b/app/code/Magento/AuthorizenetGraphQl/registration.php new file mode 100644 index 0000000000000..2e50f9fe92aaa --- /dev/null +++ b/app/code/Magento/AuthorizenetGraphQl/registration.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AuthorizenetGraphQl', __DIR__); diff --git a/app/code/Magento/Backend/App/Area/FrontNameResolver.php b/app/code/Magento/Backend/App/Area/FrontNameResolver.php index ed82c5a9c6e1e..f03e97e32d2ab 100644 --- a/app/code/Magento/Backend/App/Area/FrontNameResolver.php +++ b/app/code/Magento/Backend/App/Area/FrontNameResolver.php @@ -10,10 +10,15 @@ use Magento\Backend\Setup\ConfigOptionsList; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\RequestInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; +use Zend\Uri\Uri; /** + * Class to get area front name. + * * @api * @since 100.0.2 */ @@ -59,19 +64,35 @@ class FrontNameResolver implements \Magento\Framework\App\Area\FrontNameResolver */ private $scopeConfig; + /** + * @var Uri + */ + private $uri; + + /** + * @var RequestInterface + */ + private $request; + /** * @param \Magento\Backend\App\Config $config * @param DeploymentConfig $deploymentConfig * @param ScopeConfigInterface $scopeConfig + * @param Uri $uri + * @param RequestInterface $request */ public function __construct( \Magento\Backend\App\Config $config, DeploymentConfig $deploymentConfig, - ScopeConfigInterface $scopeConfig + ScopeConfigInterface $scopeConfig, + Uri $uri = null, + RequestInterface $request = null ) { $this->config = $config; $this->defaultFrontName = $deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_BACKEND_FRONTNAME); $this->scopeConfig = $scopeConfig; + $this->uri = $uri ?: ObjectManager::getInstance()->get(Uri::class); + $this->request = $request ?: ObjectManager::getInstance()->get(RequestInterface::class); } /** @@ -104,8 +125,8 @@ public function isHostBackend() } else { $backendUrl = $this->scopeConfig->getValue(Store::XML_PATH_UNSECURE_BASE_URL, ScopeInterface::SCOPE_STORE); } - $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''; - return stripos($this->getHostWithPort($backendUrl), $host) !== false; + $host = $this->request->getServer('HTTP_HOST', ''); + return stripos($this->getHostWithPort($backendUrl), (string) $host) !== false; } /** @@ -116,9 +137,11 @@ public function isHostBackend() */ private function getHostWithPort($url) { - $scheme = parse_url(trim($url), PHP_URL_SCHEME); - $host = parse_url(trim($url), PHP_URL_HOST); - $port = parse_url(trim($url), PHP_URL_PORT); + $this->uri->parse($url); + $scheme = $this->uri->getScheme(); + $host = $this->uri->getHost(); + $port = $this->uri->getPort(); + if (!$port) { $port = isset($this->standardPorts[$scheme]) ? $this->standardPorts[$scheme] : null; } diff --git a/app/code/Magento/Backend/Block/Dashboard/Bar.php b/app/code/Magento/Backend/Block/Dashboard/Bar.php index 7ccb2d51ccd1b..57f13c740f78e 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Bar.php +++ b/app/code/Magento/Backend/Block/Dashboard/Bar.php @@ -5,6 +5,8 @@ */ namespace Magento\Backend\Block\Dashboard; +use Magento\Store\Model\Store; + /** * Adminhtml dashboard bar block * @@ -23,6 +25,8 @@ class Bar extends \Magento\Backend\Block\Dashboard\AbstractDashboard protected $_currentCurrencyCode = null; /** + * Get totals + * * @return array */ public function getTotals() @@ -31,6 +35,8 @@ public function getTotals() } /** + * Add total + * * @param string $label * @param float $value * @param bool $isQuantity @@ -73,6 +79,7 @@ public function setCurrency($currency) * Retrieve currency model if not set then return currency model for current store * * @return \Magento\Directory\Model\Currency + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getCurrency() { @@ -90,7 +97,8 @@ public function getCurrency() $this->getRequest()->getParam('group') )->getWebsite()->getBaseCurrency(); } else { - $this->_currentCurrencyCode = $this->_storeManager->getStore()->getBaseCurrency(); + $this->_currentCurrencyCode = $this->_storeManager->getStore(Store::DEFAULT_STORE_ID) + ->getBaseCurrency(); } } diff --git a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php index 50279786c0a5b..0a73430aad0f3 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php +++ b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php @@ -43,6 +43,8 @@ public function __construct( } /** + * Construct. + * * @return void */ protected function _construct() @@ -52,6 +54,8 @@ protected function _construct() } /** + * Prepare collection. + * * @return $this */ protected function _prepareCollection() @@ -110,6 +114,8 @@ protected function _preparePage() } /** + * Prepare columns. + * * @return $this */ protected function _prepareColumns() @@ -129,7 +135,9 @@ protected function _prepareColumns() ] ); - $baseCurrencyCode = $this->_storeManager->getStore((int)$this->getParam('store'))->getBaseCurrencyCode(); + $baseCurrencyCode = $this->_storeManager->getStore( + (int)$this->getParam('store') + )->getBaseCurrencyCode(); $this->addColumn( 'total', @@ -149,7 +157,7 @@ protected function _prepareColumns() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRowUrl($row) { diff --git a/app/code/Magento/Backend/Block/Dashboard/Sales.php b/app/code/Magento/Backend/Block/Dashboard/Sales.php index 6d7a4d6458a8e..b388339460102 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Sales.php +++ b/app/code/Magento/Backend/Block/Dashboard/Sales.php @@ -39,6 +39,8 @@ public function __construct( } /** + * Prepare layout. + * * @return $this|void */ protected function _prepareLayout() diff --git a/app/code/Magento/Backend/Block/Dashboard/Tab/Products/Ordered.php b/app/code/Magento/Backend/Block/Dashboard/Tab/Products/Ordered.php index cac10ae372004..a0b1571bd17bb 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Tab/Products/Ordered.php +++ b/app/code/Magento/Backend/Block/Dashboard/Tab/Products/Ordered.php @@ -43,6 +43,8 @@ public function __construct( } /** + * Construct. + * * @return void */ protected function _construct() @@ -52,7 +54,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareCollection() { @@ -81,7 +83,7 @@ protected function _prepareCollection() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareColumns() { diff --git a/app/code/Magento/Backend/Block/Dashboard/Totals.php b/app/code/Magento/Backend/Block/Dashboard/Totals.php index 4dcda3677584c..20bcfebe31a8d 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Totals.php +++ b/app/code/Magento/Backend/Block/Dashboard/Totals.php @@ -11,6 +11,9 @@ */ namespace Magento\Backend\Block\Dashboard; +/** + * Totals block. + */ class Totals extends \Magento\Backend\Block\Dashboard\Bar { /** @@ -40,6 +43,7 @@ public function __construct( } /** + * @inheritDoc * @return $this|void */ protected function _prepareLayout() diff --git a/app/code/Magento/Backend/Block/Page/RequireJs.php b/app/code/Magento/Backend/Block/Page/RequireJs.php index 0a8e9c11ec69f..7fac44068a3fb 100644 --- a/app/code/Magento/Backend/Block/Page/RequireJs.php +++ b/app/code/Magento/Backend/Block/Page/RequireJs.php @@ -29,7 +29,6 @@ public function __construct( \Magento\Framework\Data\Form\FormKey $formKey, array $data = [] ) { - $this->formKey = $formKey; parent::__construct( $context, $data diff --git a/app/code/Magento/Backend/Block/Store/Switcher.php b/app/code/Magento/Backend/Block/Store/Switcher.php index 1468df2b0b442..9c35cfb5df81d 100644 --- a/app/code/Magento/Backend/Block/Store/Switcher.php +++ b/app/code/Magento/Backend/Block/Store/Switcher.php @@ -17,7 +17,7 @@ class Switcher extends \Magento\Backend\Block\Template /** * URL for store switcher hint */ - const HINT_URL = 'http://docs.magento.com/m2/ce/user_guide/configuration/scope.html'; + const HINT_URL = 'https://docs.magento.com/m2/ce/user_guide/configuration/scope.html'; /** * Name of website variable @@ -86,6 +86,8 @@ class Switcher extends \Magento\Backend\Block\Template protected $_storeFactory; /** + * Switcher constructor. + * * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Store\Model\WebsiteFactory $websiteFactory * @param \Magento\Store\Model\GroupFactory $storeGroupFactory @@ -106,7 +108,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -130,6 +132,8 @@ protected function _construct() } /** + * Get website collection. + * * @return \Magento\Store\Model\ResourceModel\Website\Collection */ public function getWebsiteCollection() @@ -169,6 +173,8 @@ public function isWebsiteSwitchEnabled() } /** + * Set website variable name. + * * @param string $varName * @return $this */ @@ -179,6 +185,8 @@ public function setWebsiteVarName($varName) } /** + * Get website variable name. + * * @return string */ public function getWebsiteVarName() @@ -191,6 +199,8 @@ public function getWebsiteVarName() } /** + * Check if current website selected. + * * @param \Magento\Store\Model\Website $website * @return bool */ @@ -200,6 +210,8 @@ public function isWebsiteSelected(\Magento\Store\Model\Website $website) } /** + * Return website Id. + * * @return int|null */ public function getWebsiteId() @@ -211,6 +223,8 @@ public function getWebsiteId() } /** + * Return group collection provided website. + * * @param int|\Magento\Store\Model\Website $website * @return \Magento\Store\Model\ResourceModel\Group\Collection */ @@ -247,6 +261,8 @@ public function isStoreGroupSwitchEnabled() } /** + * Sets store group variable name. + * * @param string $varName * @return $this */ @@ -257,6 +273,8 @@ public function setStoreGroupVarName($varName) } /** + * Return store group variable name. + * * @return string */ public function getStoreGroupVarName() @@ -269,6 +287,8 @@ public function getStoreGroupVarName() } /** + * Is provided group selected. + * * @param \Magento\Store\Model\Group $group * @return bool */ @@ -278,6 +298,8 @@ public function isStoreGroupSelected(\Magento\Store\Model\Group $group) } /** + * Return store group Id. + * * @return int|null */ public function getStoreGroupId() @@ -289,6 +311,8 @@ public function getStoreGroupId() } /** + * Return store collection. + * * @param \Magento\Store\Model\Group|int $group * @return \Magento\Store\Model\ResourceModel\Store\Collection */ @@ -328,6 +352,8 @@ public function getStores($group) } /** + * Return store Id. + * * @return int|null */ public function getStoreId() @@ -339,6 +365,8 @@ public function getStoreId() } /** + * Check is provided store selected. + * * @param \Magento\Store\Model\Store $store * @return bool */ @@ -358,6 +386,8 @@ public function isStoreSwitchEnabled() } /** + * Sets store variable name. + * * @param string $varName * @return $this */ @@ -368,6 +398,8 @@ public function setStoreVarName($varName) } /** + * Return store variable name. + * * @return mixed|string */ public function getStoreVarName() @@ -380,6 +412,8 @@ public function getStoreVarName() } /** + * Return switch url. + * * @return string */ public function getSwitchUrl() @@ -399,6 +433,8 @@ public function getSwitchUrl() } /** + * Checks if scope selected. + * * @return bool */ public function hasScopeSelected() @@ -472,6 +508,8 @@ public function getCurrentStoreName() } /** + * Sets store ids. + * * @param array $storeIds * @return $this */ @@ -482,6 +520,8 @@ public function setStoreIds($storeIds) } /** + * Return store ids. + * * @return array */ public function getStoreIds() @@ -490,6 +530,8 @@ public function getStoreIds() } /** + * Check if system is run in the single store mode. + * * @return bool */ public function isShow() @@ -498,6 +540,8 @@ public function isShow() } /** + * Render block. + * * @return string */ protected function _toHtml() diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Multistore.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Multistore.php index c45a222d1eb9a..424fee98d6204 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Multistore.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Multistore.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Widget\Grid\Column; /** @@ -14,15 +16,6 @@ */ class Multistore extends \Magento\Backend\Block\Widget\Grid\Column { - /** - * @param \Magento\Backend\Block\Template\Context $context - * @param array $data - */ - public function __construct(\Magento\Backend\Block\Template\Context $context, array $data = []) - { - parent::__construct($context, $data); - } - /** * Get header css class name * diff --git a/app/code/Magento/Backend/Model/Url.php b/app/code/Magento/Backend/Model/Url.php index f199fd0fe7bf1..f8210c82587b8 100644 --- a/app/code/Magento/Backend/Model/Url.php +++ b/app/code/Magento/Backend/Model/Url.php @@ -13,6 +13,7 @@ * Class \Magento\Backend\Model\UrlInterface * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -366,6 +367,20 @@ protected function _getMenu() return $this->_menu; } + /** + * Set scope entity + * + * @param mixed $scopeId + * @return \Magento\Framework\UrlInterface + * @since 101.0.3 + */ + public function setScope($scopeId) + { + parent::setScope($scopeId); + $this->_scope = $this->_scopeResolver->getScope($scopeId); + return $this; + } + /** * Set custom auth session * @@ -402,13 +417,13 @@ public function getAreaFrontName() } /** - * Retrieve action path. - * Add backend area front name as a prefix to action path + * Retrieve action path, add backend area front name as a prefix to action path * * @return string */ protected function _getActionPath() { + $path = parent::_getActionPath(); if ($path) { if ($this->getAreaFrontName()) { @@ -448,8 +463,7 @@ protected function _getConfigCacheId($path) } /** - * Get config data by path - * Use only global config values for backend + * Get config data by path, use only global config values for backend * * @param string $path * @return null|string diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminClickFormActionButtonActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminClickFormActionButtonActionGroup.xml new file mode 100644 index 0000000000000..186f11e4ea503 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminClickFormActionButtonActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickFormActionButtonActionGroup"> + <arguments> + <argument name="buttonSelector" type="string" /> + </arguments> + <waitForElementVisible selector="{{buttonSelector}}" stepKey="waitForButton"/> + <click selector="{{buttonSelector}}" stepKey="clickButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminFilterLegacyGridActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminFilterLegacyGridActionGroup.xml new file mode 100644 index 0000000000000..f4af54d2edc45 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminFilterLegacyGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFilterLegacyGridActionGroup"> + <arguments> + <argument name="fieldSelector" type="string"/> + <argument name="value" type="string"/> + <argument name="button" type="string" defaultValue="{{AdminLegacyDataGridFilterSection.apply}}"/> + </arguments> + <click selector="{{AdminLegacyDataGridFilterSection.clear}}" stepKey="resetFilters" /> + <waitForPageLoad stepKey="waitForFilterReset" /> + <fillField selector="{{field}}" userInput="{{value}}" stepKey="fillFieldInFilter"/> + <click selector="{{button}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForFiltersApply" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminResetLegacyGridFilterActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminResetLegacyGridFilterActionGroup.xml new file mode 100644 index 0000000000000..8a9ac8991bac4 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminResetLegacyGridFilterActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminResetLegacyGridFilterActionGroup"> + <arguments> + <argument name="selector" type="string" defaultValue="{{AdminLegacyDataGridFilterSection.clear}}"/> + </arguments> + <click selector="{{selector}}" stepKey="clickResetButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIsNot404ActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIsNot404ActionGroup.xml new file mode 100644 index 0000000000000..eaf7c7cd8a6ab --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIsNot404ActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminPageIsNot404ActionGroup"> + <dontSee userInput="404 Error" selector="{{AdminHeaderSection.pageHeading}}" stepKey="dontSee404PageHeading"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageInAdminPanelActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageInAdminPanelActionGroup.xml new file mode 100644 index 0000000000000..23823ea085acd --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageInAdminPanelActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageInAdminPanelActionGroup"> + <arguments> + <argument name="message" type="string" /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + + <waitForElementVisible selector="{{AdminMessagesSection.messageByType(messageType)}}" stepKey="waitForMessageVisible" /> + <see userInput="{{message}}" selector="{{AdminMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml new file mode 100644 index 0000000000000..3e3b0bc6a8a43 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertOrderGraphImageOnDashboardActionGroup"> + <click selector="{{AdminDashboardSection.ordersTab}}" stepKey="clickOrdersBtn"/> + <seeElement selector="{{AdminDashboardSection.ordersChart}}" stepKey="seeGraphImage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/ClickSaveActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/ClickSaveActionGroup.xml new file mode 100644 index 0000000000000..4fa8bf1ce6ab1 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/ClickSaveActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Click save button and see message--> + <actionGroup name="ClickSaveButtonActionGroup"> + <arguments> + <argument name="message" type="string"/> + </arguments> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitMessage" /> + <see userInput="{{message}}" selector="{{AdminMessagesSection.success}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml index 9e5c0bb3f39bf..a0492ac9b953d 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml @@ -10,15 +10,16 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SetAdminAccountActionGroup"> <arguments> - <argument name="InterfaceLocaleByValue" type="string"/> + <argument name="InterfaceLocaleByValue" defaultValue="en_US" type="string"/> </arguments> <!-- Navigate to admin System Account Page--> <amOnPage url="{{AdminSystemAccountPage.url}}" stepKey="openAdminSystemAccountPage" /> - <waitForPageLoad stepKey="loadAdminSystemAccountPage"/> + <waitForElementVisible selector="{{AdminSystemAccountSection.interfaceLocale}}" stepKey="waitForInterfaceLocale"/> <!-- Change Admin locale to Français (France) / French (France) --> <selectOption userInput="{{InterfaceLocaleByValue}}" selector="{{AdminSystemAccountSection.interfaceLocale}}" stepKey="setInterfaceLocate"/> <fillField selector="{{AdminSystemAccountSection.currentPassword}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminCustomerMainActionsSection.saveButton}}" stepKey="clickSave"/> - <waitForElement selector="{{AdminCustomerMessagesSection.successMessage}}" stepKey="waitSuccessMessage"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the account." stepKey="seeSuccessMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Data/CookieConfigData.xml b/app/code/Magento/Backend/Test/Mftf/Data/CookieConfigData.xml new file mode 100644 index 0000000000000..52a6c27a37ea8 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Data/CookieConfigData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ChangedCookieDomainForMainWebsiteConfigData"> + <data key="path">web/cookie/cookie_domain</data> + <data key="scope">website</data> + <data key="scope_code">base</data> + <data key="value">testDomain.com</data> + </entity> + <entity name="EmptyCookieDomainForMainWebsiteConfigData"> + <data key="path">web/cookie/cookie_domain</data> + <data key="scope">website</data> + <data key="scope_code">base</data> + <data key="value">''</data> + </entity> +</entities> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml index ed30395406f7d..0e95d5c139a1b 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminDashboardPage" url="admin/dashboard/" area="admin" module="Magento_Backend"> <section name="AdminMenuSection"/> + <section name="AdminDashboardSection"/> </page> </pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml index 664c335a4cfc6..61fe7ffa48e23 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml @@ -9,6 +9,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDashboardSection"> + <element name="ordersTab" type="button" selector="#diagram_tab_orders"/> + <element name="ordersChart" type="button" selector="#diagram_tab_orders_content .dashboard-diagram-image img"/> <element name="dashboardDiagramContent" type="button" selector="#diagram_tab_content"/> <element name="dashboardDiagramOrderContentTab" type="block" selector="#diagram_tab_orders_content"/> <element name="dashboardDiagramAmounts" type="button" selector="#diagram_tab_amounts"/> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml index 5b517c7be8a79..186bb183d68d6 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml @@ -11,5 +11,9 @@ <section name="AdminHeaderSection"> <element name="pageTitle" type="text" selector=".page-header h1.page-title"/> <element name="adminUserAccountText" type="text" selector=".page-header .admin-user-account-text" /> + <!-- Legacy heading section. Mostly used for admin 404 and 403 pages --> + <element name="pageHeading" type="text" selector=".page-content .page-heading"/> + <!-- Used for page not found error --> + <element name="pageNotFoundTitle" type="text" selector=".page-title span"/> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridFilterSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridFilterSection.xml new file mode 100644 index 0000000000000..0d5f3f3e53e50 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridFilterSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLegacyDataGridFilterSection"> + <element name="filterForm" type="block" selector="[data-role='filter-form']" /> + <element name="inputFieldByNameAttr" type="input" selector="[data-role='filter-form'] input[name='{{inputNameAttr}}']" parameterized="true" /> + <element name="apply" type="button" selector=".admin__data-grid-header [data-action='grid-filter-apply']" /> + <element name="clear" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" /> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridTableSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridTableSection.xml new file mode 100644 index 0000000000000..ad36001928863 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLegacyDataGridTableSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLegacyDataGridTableSection"> + <element name="rowTemplateStrict" type="block" selector="//tbody/tr[td[text()[normalize-space()='{{text}}']]]" parameterized="true" /> + <element name="rowTemplate" type="block" selector="//tbody/tr[td[contains(.,normalize-space('{{text}}'))]]" parameterized="true" /> + <element name="columnTemplateStrict" type="block" selector="//tbody/tr[td[contains(.,normalize-space('{{text}}'))]]/td[@data-column='{{dataColumn}}']" parameterized="true" /> + <element name="columnTemplate" type="block" selector="//tbody/tr[td[contains(.,normalize-space('{{text}}'))]]/td[@data-column='{{dataColumn}}']" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml index 4867b5ba5ae08..291f61e17a508 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml @@ -13,5 +13,7 @@ <element name="saveAndContinue" type="button" selector="button[id*=save_and_continue]" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> <element name="add" type="button" selector="#add" timeout="30"/> + <element name="cancelDelete" type="button" selector=".modal-popup.confirm button.action-dismiss" timeout="10"/> + <element name="confirmDelete" type="button" selector=".modal-popup.confirm button.action-accept" timeout="10"/> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml index 88e740d689cdd..be3ef92acf0ac 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml @@ -13,5 +13,6 @@ <element name="nthSuccess" type="text" selector=".message.message-success.success:nth-of-type({{n}})>div" parameterized="true"/> <element name="error" type="text" selector="#messages div.message-error"/> <element name="notice" type="text" selector=".message.message-notice.notice"/> + <element name="messageByType" type="text" selector="#messages div.message-{{messageType}}" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml index a01e025ba3dca..2f799721a8cef 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminSlideOutDialogSection"> - <element name="closeButton" type="button" selector=".modal-slide._show [data-role='closeBtn']" timeout="30"/> + <element name="closeButton" type="button" selector=".modal-slide._show [data-role="closeBtn"]" timeout="30"/> <element name="cancelButton" type="button" selector="//*[contains(@class, 'modal-slide') and contains(@class, '_show')]//*[contains(@class, 'page-actions')]//button[normalize-space(.)='Cancel']" timeout="30"/> <element name="doneButton" type="button" selector="//*[contains(@class, 'modal-slide') and contains(@class, '_show')]//*[contains(@class, 'page-actions')]//button[normalize-space(.)='Done']" timeout="30"/> <element name="saveButton" type="button" selector="//*[contains(@class, 'modal-slide') and contains(@class, '_show')]//*[contains(@class, 'page-actions')]//button[normalize-space(.)='Save']" timeout="30"/> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml index b9570ce945943..a78b381b6e712 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminSystemAccountSection"> - <element name="interfaceLocale" type="text" selector="#interface_locale"/> + <element name="interfaceLocale" type="select" selector="#interface_locale"/> <element name="currentPassword" type="text" selector="#current_password"/> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml index 55cb5a71505a5..f48c7752efc7a 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml @@ -11,15 +11,16 @@ <test name="AdminDashboardWithChartsTest"> <annotations> <features value="Backend"/> - <title value="Google chart on Magento dashboard"/> - <description value="Google chart on Magento dashboard page is not broken"/> + <stories value="Google Charts on Magento dashboard"/> + <title value="Admin should see Google chart on Magento dashboard"/> + <description value="Google chart on Magento dashboard page is displaying properly"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-98934"/> <useCaseId value="MAGETWO-98584"/> <group value="backend"/> </annotations> <before> - <magentoCLI command="config:set admin/dashboard/enable_charts 1" stepKey="setEnableCharts" /> + <magentoCLI command="config:set admin/dashboard/enable_charts 1" stepKey="setEnableCharts"/> <createData entity="SimpleProduct2" stepKey="createProduct"> <field key="price">150</field> </createData> @@ -33,7 +34,7 @@ <comment userInput="Reset admin order filter" stepKey="resetAdminOrderFilter"/> <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingOrderGrid"/> - <magentoCLI command="config:set admin/dashboard/enable_charts 0" stepKey="setDisableChartsAsDefault" /> + <magentoCLI command="config:set admin/dashboard/enable_charts 0" stepKey="setDisableChartsAsDefault"/> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <actionGroup ref="logout" stepKey="logout"/> @@ -47,7 +48,7 @@ <!-- Login as customer --> <comment userInput="Login as customer" stepKey="loginAsCustomer"/> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> - <argument name="Customer" value="$$createCustomer$$" /> + <argument name="Customer" value="$$createCustomer$$"/> </actionGroup> <!-- Add Product to Shopping Cart--> <comment userInput="Add product to the shopping cart" stepKey="addProductToCart"/> @@ -82,7 +83,9 @@ <!-- Create invoice --> <comment userInput="Create invoice" stepKey="createInvoice"/> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> + <waitForPageLoad stepKey="waitForInvoicePageToLoad"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoiceInPageTitle" after="clickInvoiceButton"/> <see selector="{{AdminInvoiceTotalSection.total('Subtotal')}}" userInput="$150.00" stepKey="seeCorrectGrandTotal"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterChangeCookieDomainTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterChangeCookieDomainTest.xml new file mode 100644 index 0000000000000..93d411c8827ed --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterChangeCookieDomainTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginAfterChangeCookieDomainTest"> + <annotations> + <features value="Backend"/> + <stories value="Login on the Admin Backend"/> + <title value="Admin user can login after changing cookie domain on main website scope without changing cookie domain on default scope"/> + <description value="Admin user can login after changing cookie domain on main website scope without changing cookie domain on default scope"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-17847"/> + <useCaseId value="MC-17275"/> + <group value="backend"/> + </annotations> + <before> + <magentoCLI command="config:set {{ChangedCookieDomainForMainWebsiteConfigData.path}} --scope={{ChangedCookieDomainForMainWebsiteConfigData.scope}} --scope-code={{ChangedCookieDomainForMainWebsiteConfigData.scope_code}} {{ChangedCookieDomainForMainWebsiteConfigData.value}}" stepKey="changeDomainForMainWebsiteBeforeTestRun"/> + <magentoCLI command="cache:flush config" stepKey="flushCacheBeforeTestRun"/> + </before> + <after> + <magentoCLI command="config:set {{EmptyCookieDomainForMainWebsiteConfigData.path}} --scope={{EmptyCookieDomainForMainWebsiteConfigData.scope}} --scope-code={{EmptyCookieDomainForMainWebsiteConfigData.scope_code}} {{EmptyCookieDomainForMainWebsiteConfigData.value}}" stepKey="changeDomainForMainWebsiteAfterTestComplete"/> + <magentoCLI command="cache:flush config" stepKey="flushCacheAfterTestComplete"/> + </after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml new file mode 100644 index 0000000000000..38749dfd792ca --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginAfterJSMinificationTest"> + <annotations> + <features value="Backend"/> + <stories value="Admin Panel JS minification"/> + <title value="Admin panel should be accessible with JS minification enabled"/> + <description value="Admin panel should be accessible with JS minification enabled"/> + <testCaseId value="MC-14104"/> + <severity value="MAJOR"/> + <group value="backend"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <magentoCLI command="config:set {{MinifyJavaScriptFilesEnableConfigData.path}} {{MinifyJavaScriptFilesEnableConfigData.value}}" stepKey="enableJsMinification"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set {{MinifyJavaScriptFilesDisableConfigData.path}} {{MinifyJavaScriptFilesDisableConfigData.value}}" stepKey="disableJsMinification"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="loggedInSuccessfully"/> + <actionGroup ref="AssertAdminPageIsNot404ActionGroup" stepKey="dontSee404Page"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index 7f0194b7dc347..ce33f01c60141 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminLoginTest"> <annotations> <features value="Backend"/> @@ -20,11 +20,8 @@ <group value="login"/> </annotations> - <amOnPage url="{{AdminLoginPage.url}}" stepKey="amOnAdminLoginPage"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickOnSignIn"/> - <closeAdminNotification stepKey="closeAdminNotification"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> </test> -</tests> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminPrivacyPolicyTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminPrivacyPolicyTest.xml new file mode 100644 index 0000000000000..4f215d20a7a36 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminPrivacyPolicyTest.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminPrivacyPolicyTest"> + <annotations> + <features value="Backend"/> + <stories value="Checks to see if privacy policy url is in the admin page and every sub page"/> + <title value="There should be a privacy policy url in the admin page and every sub page"/> + <description value="There should be a privacy policy url in the admin page and every sub page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-17787"/> + <group value="backend"/> + <group value="login"/> + </annotations> + + <!-- Logging in Magento admin and checking for Privacy policy footer in dashboard --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkDashboard"/> + + <!-- Checking for Privacy policy footer in salesOrderPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesOrder"> + <argument name="menuUiId" value="magento-sales-sales"/> + <argument name="submenuUiId" value="magento-sales-sales-order"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkSalesOrder"/> + + <!-- Checking for Privacy policy footer in catalogProductsPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCatalogProducts"> + <argument name="menuUiId" value="magento-catalog-catalog"/> + <argument name="submenuUiId" value="magento-catalog-catalog-products"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkCatalogProducts"/> + + <!-- Checking for Privacy policy footer in customersAllCustomersPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCustomersAllCustomers"> + <argument name="menuUiId" value="magento-customer-customer"/> + <argument name="submenuUiId" value="magento-customer-customer-manage"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkCustomersAllCustomers"/> + + <!-- Checking for Privacy policy footer in marketingCatalogPriceRulePage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingCatalogPriceRule"> + <argument name="menuUiId" value="magento-backend-marketing"/> + <argument name="submenuUiId" value="magento-catalogrule-promo-catalog"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkMarketingCatalogPriceRule"/> + + <!-- Checking for Privacy policy footer in contentBlocksPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentBlocks"> + <argument name="menuUiId" value="magento-backend-content"/> + <argument name="submenuUiId" value="magento-cms-cms-block"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkContentBlocks"/> + + <!-- Checking for Privacy policy footer in reportSearcbTermsPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsSearchTerms"> + <argument name="menuUiId" value="magento-reports-report"/> + <argument name="submenuUiId" value="magento-search-report-search-term"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkReportsSearchTerms"/> + + <!-- Checking for Privacy policy footer in storesAllStoresPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresAllStores"> + <argument name="menuUiId" value="magento-backend-stores"/> + <argument name="submenuUiId" value="magento-backend-system-store"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkStoresAllStores"/> + + <!-- Checking for Privacy policy footer in systemImportPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemImport"> + <argument name="menuUiId" value="magento-backend-system"/> + <argument name="submenuUiId" value="magento-importexport-system-convert-import"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkSystemImport"/> + + <!-- Checking for Privacy policy footer in findPartnersAndExtensionsPage --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToFindPartnersAndExtensions"> + <argument name="menuUiId" value="magento-marketplace-partners"/> + <argument name="submenuUiId" value="magento-marketplace-partners"/> + </actionGroup> + <seeLink userInput="Privacy Policy" url="https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf" stepKey="seePrivacyPolicyLinkFindPartnersAndExtensions"/> + </test> +</tests> + + diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml index 5485dcaea33ee..df5ca6d037813 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml @@ -11,9 +11,11 @@ <test name="AdminUserLoginWithStoreCodeInUrlTest"> <annotations> <features value="Backend"/> + <stories value="Admin Panel URL with Store Code"/> <title value="Admin panel should be accessible with Add Store Code to URL setting enabled"/> <description value="Admin panel should be accessible with Add Store Code to URL setting enabled"/> - <testCaseId value="MC-14279" /> + <testCaseId value="MC-14279"/> + <severity value="CRITICAL"/> <group value="backend"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php index 642c6283decae..ba0b01d4055de 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php @@ -28,6 +28,16 @@ class FrontNameResolverTest extends \PHPUnit\Framework\TestCase */ protected $scopeConfigMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\Uri\Uri + */ + protected $uri; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\Request\Http + */ + protected $request; + /** * @var string */ @@ -41,9 +51,19 @@ protected function setUp() ->method('get') ->with(ConfigOptionsList::CONFIG_PATH_BACKEND_FRONTNAME) ->will($this->returnValue($this->_defaultFrontName)); + $this->uri = $this->createMock(\Zend\Uri\Uri::class); + + $this->request = $this->createMock(\Magento\Framework\App\Request\Http::class); + $this->configMock = $this->createMock(\Magento\Backend\App\Config::class); $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); - $this->model = new FrontNameResolver($this->configMock, $deploymentConfigMock, $this->scopeConfigMock); + $this->model = new FrontNameResolver( + $this->configMock, + $deploymentConfigMock, + $this->scopeConfigMock, + $this->uri, + $this->request + ); } public function testIfCustomPathUsed() @@ -93,7 +113,6 @@ public function testIfCustomPathNotUsed() */ public function testIsHostBackend($url, $host, $useCustomAdminUrl, $customAdminUrl, $expectedValue) { - $_SERVER['HTTP_HOST'] = $host; $this->scopeConfigMock->expects($this->exactly(2)) ->method('getValue') ->will( @@ -115,6 +134,41 @@ public function testIsHostBackend($url, $host, $useCustomAdminUrl, $customAdminU ] ) ); + + $this->request->expects($this->any()) + ->method('getServer') + ->will($this->returnValue($host)); + + $urlParts = []; + $this->uri->expects($this->once()) + ->method('parse') + ->willReturnCallback( + function ($url) use (&$urlParts) { + $urlParts = parse_url($url); + } + ); + $this->uri->expects($this->once()) + ->method('getScheme') + ->willReturnCallback( + function () use (&$urlParts) { + return array_key_exists('scheme', $urlParts) ? $urlParts['scheme'] : ''; + } + ); + $this->uri->expects($this->once()) + ->method('getHost') + ->willReturnCallback( + function () use (&$urlParts) { + return array_key_exists('host', $urlParts) ? $urlParts['host'] : ''; + } + ); + $this->uri->expects($this->once()) + ->method('getPort') + ->willReturnCallback( + function () use (&$urlParts) { + return array_key_exists('port', $urlParts) ? $urlParts['port'] : ''; + } + ); + $this->assertEquals($this->model->isHostBackend(), $expectedValue); } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php index 1bf23649e2ea8..5aa8cf7a0c579 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php @@ -8,6 +8,9 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +/** + * Unit test for \Magento\Backend\Block\Widget\Grid\Column\Filter\Text + */ class TextTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Backend\Block\Widget\Grid\Column\Filter\Text*/ @@ -31,7 +34,10 @@ protected function setUp() ->setMethods(['getEscaper']) ->disableOriginalConstructor() ->getMock(); - $this->escaper = $this->createPartialMock(\Magento\Framework\Escaper::class, ['escapeHtml']); + $this->escaper = $this->createPartialMock( + \Magento\Framework\Escaper::class, + ['escapeHtml', 'escapeHtmlAttr'] + ); $this->helper = $this->createMock(\Magento\Framework\DB\Helper::class); $this->context->expects($this->once())->method('getEscaper')->willReturn($this->escaper); @@ -60,6 +66,13 @@ public function testGetHtml() $this->block->setColumn($column); $this->escaper->expects($this->any())->method('escapeHtml')->willReturn('escapedHtml'); + $this->escaper->expects($this->once()) + ->method('escapeHtmlAttr') + ->willReturnCallback( + function ($string) { + return $string; + } + ); $column->expects($this->any())->method('getId')->willReturn('id'); $column->expects($this->once())->method('getHtmlId')->willReturn('htmlId'); diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index c6075e806c7f5..4f8cb5905adb7 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backup": "100.3.*", "magento/module-catalog": "103.0.*", @@ -40,5 +40,5 @@ "Magento\\Backend\\": "" } }, - "version": "101.0.2" + "version": "101.0.3" } diff --git a/app/code/Magento/Backend/etc/adminhtml/di.xml b/app/code/Magento/Backend/etc/adminhtml/di.xml index 4abea272c5495..5f566396ab500 100644 --- a/app/code/Magento/Backend/etc/adminhtml/di.xml +++ b/app/code/Magento/Backend/etc/adminhtml/di.xml @@ -86,6 +86,7 @@ <arguments> <argument name="lifetimePath" xsi:type="const">Magento\Backend\Model\Auth\Session::XML_PATH_SESSION_LIFETIME</argument> <argument name="sessionName" xsi:type="const">Magento\Backend\Model\Session\AdminConfig::SESSION_NAME_ADMIN</argument> + <argument name="scopeType" xsi:type="const">Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT</argument> </arguments> </type> <type name="Magento\Framework\View\Result\PageFactory"> diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index c762dbf58de62..2a7075b4dfb29 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -583,11 +583,6 @@ <label>Validate HTTP_USER_AGENT</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="use_frontend_sid" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> - <label>Use SID on Storefront</label> - <comment>Allows customers to stay logged in when switching between different stores.</comment> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> </group> </section> </system> diff --git a/app/code/Magento/Backend/view/adminhtml/layout/default.xml b/app/code/Magento/Backend/view/adminhtml/layout/default.xml index e80932e5039f8..a7faab0bc4673 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/default.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/default.xml @@ -60,11 +60,17 @@ </container> <container name="legal.system" htmlTag="div" htmlClass="footer-legal-system col-m-6"> <block class="Magento\Backend\Block\Page\Footer" name="version" as="version" /> + <block class="Magento\Framework\View\Element\Template" name="privacyPolicy" as="privacyPolicy" template="Magento_Backend::page/privacyPolicy.phtml"> + <arguments> + <argument name="privacypolicy_url" xsi:type="string">https://magento.com/sites/default/files/REVISED-MAGENTO-PRIVACY-POLICY.pdf</argument> + </arguments> + </block> <block class="Magento\Framework\View\Element\Template" name="report" as="report" template="Magento_Backend::page/report.phtml"> <arguments> <argument name="bugreport_url" xsi:type="string">https://github.com/magento/magento2/issues</argument> </arguments> </block> + </container> </container> </referenceContainer> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml index 843328fbf17d7..be309423c48d2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml @@ -3,14 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** * @see \Magento\Backend\Block\Denied */ + +// phpcs:disable Magento2.Security.Superglobal ?> <hr class="access-denied-hr"/> <div class="access-denied-page"> @@ -21,10 +20,10 @@ <li><span><?= $block->escapeHtml(__('Contact a system administrator or store owner to gain permissions.')) ?></span></li> <li> <span><?= $block->escapeHtml(__('Return to ')) ?> - <?php if(isset($_SERVER['HTTP_REFERER'])): ?> + <?php if (isset($_SERVER['HTTP_REFERER'])) : ?> <a href="<?= $block->escapeUrl(__($_SERVER['HTTP_REFERER'])) ?>"> <?= $block->escapeHtml(__('previous page')) ?></a><?= $block->escapeHtml(__('.')) ?> - <?php else: ?> + <?php else : ?> <a href="<?= $block->escapeHtmlAttr(__('javascript:history.back()')) ?>"> <?= $block->escapeHtml(__('previous page')) ?></a><?= $block->escapeHtml(__('.')) ?> <?php endif ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/formkey.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/formkey.phtml index 9629db9fa455b..edc14190e4edf 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/formkey.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/formkey.phtml @@ -4,4 +4,4 @@ * See COPYING.txt for license details. */ ?> -<div><input name="form_key" type="hidden" value="<?= /* @escapeNotVerified */ $block->getFormKey() ?>" /></div> +<div><input name="form_key" type="hidden" value="<?= $block->escapeHtmlAttr($block->getFormKey()) ?>" /></div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml index 52d5dd6d114ee..1d05450f44c99 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml @@ -4,19 +4,20 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** + * @var \Magento\Framework\View\Element\AbstractBlock $block + */ ?> <form method="post" action="" id="login-form" data-mage-init='{"form": {}, "validation": {}}' autocomplete="off"> <fieldset class="admin__fieldset"> <legend class="admin__legend"> - <span><?= /* @escapeNotVerified */ __('Welcome, please sign in') ?></span> + <span><?= $block->escapeHtml(__('Welcome, please sign in')) ?></span> </legend><br/> - <input name="form_key" type="hidden" value="<?= /* @escapeNotVerified */ $block->getFormKey() ?>" /> + <input name="form_key" type="hidden" value="<?= $block->escapeHtmlAttr($block->getFormKey()) ?>" /> <div class="admin__field _required field-username"> <label for="username" class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('Username') ?></span> + <span><?= $block->escapeHtml(__('Username')) ?></span> </label> <div class="admin__field-control"> <input id="username" @@ -26,14 +27,14 @@ autofocus value="" data-validate="{required:true}" - placeholder="<?= /* @escapeNotVerified */ __('user name') ?>" + placeholder="<?= $block->escapeHtmlAttr(__('user name')) ?>" autocomplete="off" /> </div> </div> <div class="admin__field _required field-password"> <label for="login" class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('Password') ?></span> + <span><?= $block->escapeHtml(__('Password')) ?></span> </label> <div class="admin__field-control"> <input id="login" @@ -42,7 +43,7 @@ name="login[password]" data-validate="{required:true}" value="" - placeholder="<?= /* @escapeNotVerified */ __('password') ?>" + placeholder="<?= $block->escapeHtmlAttr(__('password')) ?>" autocomplete="off" /> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login_buttons.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login_buttons.phtml index 2459bc54e0c34..18ee8a86517c1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login_buttons.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login_buttons.phtml @@ -8,6 +8,6 @@ <button <?php $block->getUiId(); ?> class="action-login action-primary"> - <span><?= /* @escapeNotVerified */ __('Sign in') ?></span> + <span><?= $block->escapeHtml(__('Sign in')) ?></span> </button> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml index 93509cc62f7d5..ac81861c9930d 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/overlay_popup.phtml @@ -3,15 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div class="wrapper-popup"> <div class="middle" id="anchor-content"> <div id="page:main-container"> - <?php if ($block->getChildHtml('left')): ?> - <div class="columns <?= /* @escapeNotVerified */ $block->getContainerCssClass() ?>" id="page:container"> + <?php if ($block->getChildHtml('left')) : ?> + <div class="columns <?= $block->escapeHtmlAttr($block->getContainerCssClass()) ?>" id="page:container"> <div id="page:left" class="side-col"> <?= $block->getChildHtml('left') ?> </div> @@ -24,13 +21,13 @@ </div> </div> </div> - <?php else: ?> + <?php else : ?> <div id="messages" data-container-for="messages"><?= $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?></div> <?= $block->getChildHtml('content') ?> - <?php endif; ?> + <?php endif; ?> </div> </div> - <?php if ($block->getChildHtml('footer')): ?> + <?php if ($block->getChildHtml('footer')) : ?> <div class="footer"> <?= $block->getChildHtml('footer') ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/page.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/page.phtml index ebb8e26c93f9d..d8cab67ecb79b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/page.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/page.phtml @@ -3,19 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Backend\Block\Page */ ?> <!doctype html> -<html lang="<?= /* @escapeNotVerified */ $block->getLang() ?>" class="no-js"> +<html lang="<?= $block->escapeHtmlAttr($block->getLang()) ?>" class="no-js"> <head> <?= $block->getChildHtml('head') ?> </head> -<body id="html-body"<?= $block->getBodyClass() ? ' class="' . $block->getBodyClass() . '"' : '' ?> data-container="body" data-mage-init='{"loaderAjax":{},"loader":{}}'> +<body id="html-body" class="<?= $block->escapeHtmlAttr($block->getBodyClass()) ?>" data-container="body" data-mage-init='{"loaderAjax":{},"loader":{}}'> <div class="page-wrapper"> <?= $block->getChildHtml('notification_window') ?> <?= $block->getChildHtml('global_notices') ?> @@ -31,8 +28,8 @@ <?= $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> </div> <?= $block->getChildHtml('page_main_actions') ?> - <?php if ($block->getChildHtml('left')): ?> - <div id="page:main-container" class="<?= /* @escapeNotVerified */ $block->getContainerCssClass() ?> col-2-left-layout"> + <?php if ($block->getChildHtml('left')) : ?> + <div id="page:main-container" class="<?= $block->escapeHtmlAttr($block->getContainerCssClass()) ?> col-2-left-layout"> <div class="main-col" id="content"> <?= $block->getChildHtml('content') ?> </div> @@ -41,7 +38,7 @@ <?= $block->getChildHtml('left') ?> </div> </div> - <?php else: ?> + <?php else : ?> <div id="page:main-container" class="col-1-layout"> <?= $block->getChildHtml('content') ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml index ae123511bd478..12b388c210774 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml @@ -3,33 +3,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div class="dashboard-diagram"> <div class="dashboard-diagram-switcher"> <label for="order_<?= $block->getHtmlId() ?>_period" - class="label"><?= /* @escapeNotVerified */ __('Select Range:') ?></label> + class="label"><?= $block->escapeHtml(__('Select Range:')) ?></label> <select name="period" id="order_<?= $block->getHtmlId() ?>_period" onchange="changeDiagramsPeriod(this);" class="admin__control-select"> - <?php foreach ($this->helper('Magento\Backend\Helper\Dashboard\Data')->getDatePeriods() as $value => $label): ?> - <?php if (in_array($value, ['custom'])) { + <?php //phpcs:disable ?> + <?php foreach ($this->helper(\Magento\Backend\Helper\Dashboard\Data::class)->getDatePeriods() as $value => $label) : ?> + <?php + //phpcs:enable + if (in_array($value, ['custom'])) { continue; } ?> - <option value="<?= /* @escapeNotVerified */ $value ?>" - <?php if ($block->getRequest()->getParam('period') == $value): ?> selected="selected"<?php endif; ?> - ><?= /* @escapeNotVerified */ $label ?></option> + <option value="<?= /* @noEscape */ $value ?>" + <?php if ($block->getRequest()->getParam('period') == $value) : ?> selected="selected"<?php endif; ?> + ><?= $block->escapeHtml($label) ?></option> <?php endforeach; ?> </select> </div> - <?php if ($block->getCount()): ?> + <?php if ($block->getCount()) : ?> <div class="dashboard-diagram-image"> - <img src="<?= /* @escapeNotVerified */ $block->getChartUrl(false) ?>" class="dashboard-diagram-chart" alt="Chart" title="Chart" /> + <img src="<?= $block->escapeUrl($block->getChartUrl(false)) ?>" class="dashboard-diagram-chart" alt="Chart" title="Chart" /> </div> - <?php else: ?> + <?php else : ?> <div class="dashboard-diagram-nodata"> - <span><?= /* @escapeNotVerified */ __('No Data Found') ?></span> + <span><?= $block->escapeHtml(__('No Data Found')) ?></span> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml index 7dddc15121831..f8e584ce5b9cd 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile ?> <div class="dashboard-diagram-disabled"> - <?= /* @escapeNotVerified */ __('Chart is disabled. To enable the chart, click <a href="%1">here</a>.', $block->getConfigUrl()) ?> + <?= /* @noEscape */ __('Chart is disabled. To enable the chart, click <a href="%1">here</a>.', $block->escapeUrl($block->getConfigUrl())) ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/grid.phtml index 1041aef59ceac..7c05335642ba7 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/grid.phtml @@ -3,90 +3,87 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php -$numColumns = sizeof($block->getColumns()); +$numColumns = count($block->getColumns()); ?> -<?php if ($block->getCollection()): ?> +<?php if ($block->getCollection()) : ?> <div class="dashboard-item-content"> - <?php if ($block->getCollection()->getSize()>0): ?> - <table class="admin__table-primary dashboard-data" id="<?= /* @escapeNotVerified */ $block->getId() ?>_table"> + <?php if ($block->getCollection()->getSize() > 0) : ?> + <table class="admin__table-primary dashboard-data" id="<?= $block->escapeHtmlAttr($block->getId()) ?>_table"> <?php /* This part is commented to remove all <col> tags from the code. */ /* foreach ($block->getColumns() as $_column): ?> <col <?= $_column->getHtmlProperty() ?> /> <?php endforeach; */ ?> - <?php if ($block->getHeadersVisibility() || $block->getFilterVisibility()): ?> + <?php if ($block->getHeadersVisibility() || $block->getFilterVisibility()) : ?> <thead> - <?php if ($block->getHeadersVisibility()): ?> + <?php if ($block->getHeadersVisibility()) : ?> <tr> - <?php foreach ($block->getColumns() as $_column): ?> + <?php foreach ($block->getColumns() as $_column) : ?> <?= $_column->getHeaderHtml() ?> <?php endforeach; ?> </tr> <?php endif; ?> </thead> <?php endif; ?> - <?php if (!$block->getIsCollapsed()): ?> + <?php if (!$block->getIsCollapsed()) : ?> <tbody> - <?php foreach ($block->getCollection() as $_index => $_item): ?> - <tr title="<?= /* @escapeNotVerified */ $block->getRowUrl($_item) ?>"> - <?php $i = 0; foreach ($block->getColumns() as $_column): ?> - <td class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> <?= ++$i == $numColumns ? 'last' : '' ?>"><?= (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?></td> + <?php foreach ($block->getCollection() as $_index => $_item) : ?> + <tr title="<?= $block->escapeHtmlAttr($block->getRowUrl($_item)) ?>"> + <?php $i = 0; foreach ($block->getColumns() as $_column) : ?> + <td class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> <?= /* @noEscape */ ++$i == $numColumns ? 'last' : '' ?>"><?= /* @noEscape */ (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?></td> <?php endforeach; ?> </tr> <?php endforeach; ?> </tbody> <?php endif; ?> </table> - <?php else: ?> - <div class="<?= /* @escapeNotVerified */ $block->getEmptyTextClass() ?>"><?= /* @escapeNotVerified */ $block->getEmptyText() ?></div> + <?php else : ?> + <div class="<?= $block->escapeHtmlAttr($block->getEmptyTextClass()) ?>"><?= $block->escapeHtml($block->getEmptyText()) ?></div> <?php endif; ?> </div> -<?php if ($block->canDisplayContainer()): ?> + <?php if ($block->canDisplayContainer()) : ?> <script> var deps = []; -<?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> deps.push('uiRegistry'); -<?php endif; ?> + <?php endif; ?> -<?php if (strpos($block->getRowClickCallback(), 'order.') !== false): ?> + <?php if (strpos($block->getRowClickCallback(), 'order.') !== false) : ?> deps.push('Magento_Sales/order/create/form'); -<?php endif; ?> + <?php endif; ?> deps.push('mage/adminhtml/grid'); require(deps, function(<?= ($block->getDependencyJsObject() ? 'registry' : '') ?>){ - <?php //TODO: getJsObjectName and getRowClickCallback has unexpected behavior. Should be removed ?> + <?php //TODO: getJsObjectName and getRowClickCallback has unexpected behavior. Should be removed ?> - <?php if ($block->getDependencyJsObject()): ?> - registry.get('<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>', function (<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>) { - <?php endif; ?> + <?php if ($block->getDependencyJsObject()) : ?> + registry.get('<?= $block->escapeJs($block->getDependencyJsObject()) ?>', function (<?= $block->escapeJs($block->getDependencyJsObject()) ?>) { + <?php endif; ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?> = new varienGrid('<?= /* @escapeNotVerified */ $block->getId() ?>', '<?= /* @escapeNotVerified */ $block->getGridUrl() ?>', '<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameSort() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameDir() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameFilter() ?>'); - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.useAjax = '<?= /* @escapeNotVerified */ $block->getUseAjax() ?>'; - <?php if ($block->getRowClickCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.rowClickCallback = <?= /* @escapeNotVerified */ $block->getRowClickCallback() ?>; - <?php endif; ?> - <?php if ($block->getCheckboxCheckCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.checkboxCheckCallback = <?= /* @escapeNotVerified */ $block->getCheckboxCheckCallback() ?>; - <?php endif; ?> - <?php if ($block->getRowInitCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.initRowCallback = <?= /* @escapeNotVerified */ $block->getRowInitCallback() ?>; - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.rows.each(function(row){<?= /* @escapeNotVerified */ $block->getRowInitCallback() ?>(<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>, row)}); - <?php endif; ?> - <?php if ($block->getMassactionBlock()->isAvailable()): ?> - <?= /* @escapeNotVerified */ $block->getMassactionBlock()->getJavaScript() ?> - <?php endif ?> + <?= $block->escapeJs($block->getJsObjectName()) ?> = new varienGrid('<?= $block->escapeJs($block->getId()) ?>', '<?= $block->escapeJs($block->getGridUrl()) ?>', '<?= $block->escapeJs($block->getVarNamePage()) ?>', '<?= $block->escapeJs($block->getVarNameSort()) ?>', '<?= $block->escapeJs($block->getVarNameDir()) ?>', '<?= $block->escapeJs($block->getVarNameFilter()) ?>'); + <?= $block->escapeJs($block->getJsObjectName()) ?>.useAjax = '<?= $block->escapeJs($block->getUseAjax()) ?>'; + <?php if ($block->getRowClickCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.rowClickCallback = <?= /* @noEscape */ $block->getRowClickCallback() ?>; + <?php endif; ?> + <?php if ($block->getCheckboxCheckCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.checkboxCheckCallback = <?= /* @noEscape */ $block->getCheckboxCheckCallback() ?>; + <?php endif; ?> + <?php if ($block->getRowInitCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.initRowCallback = <?= /* @noEscape */ $block->getRowInitCallback() ?>; + <?= $block->escapeJs($block->getJsObjectName()) ?>.rows.each(function(row){<?= /* @noEscape */ $block->getRowInitCallback() ?>(<?= $block->escapeJs($block->getJsObjectName()) ?>, row)}); + <?php endif; ?> + <?php if ($block->getMassactionBlock()->isAvailable()) : ?> + <?= /* @noEscape */ $block->getMassactionBlock()->getJavaScript() ?> + <?php endif ?> - <?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> }); - <?php endif; ?> + <?php endif; ?> }); </script> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml index 865e0fac38314..6152c8fe1cff1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php if (is_array($block->getChildBlock('diagrams')->getTabsIds())) : ?> @@ -17,13 +14,13 @@ require([ window.changeDiagramsPeriod = function(periodObj) { periodParam = periodObj.value ? 'period/' + periodObj.value + '/' : ''; -<?php foreach ($block->getChildBlock('diagrams')->getTabsIds() as $tabId): ?> - ajaxBlockParam = 'block/tab_<?= /* @escapeNotVerified */ $tabId ?>/'; - ajaxBlockUrl = '<?= $block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => '', 'period' => '']) ?>' + ajaxBlockParam + periodParam; + <?php foreach ($block->getChildBlock('diagrams')->getTabsIds() as $tabId) : ?> + ajaxBlockParam = 'block/tab_<?= $block->escapeJs($tabId) ?>/'; + ajaxBlockUrl = '<?= $block->escapeJs($block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => '', 'period' => ''])) ?>' + ajaxBlockParam + periodParam; new Ajax.Request(ajaxBlockUrl, { parameters: {isAjax: 'true', form_key: FORM_KEY}, onSuccess: function(transport) { - tabContentElementId = '<?= /* @escapeNotVerified */ $block->getChildBlock('diagrams')->getId() ?>_<?= /* @escapeNotVerified */ $tabId ?>_content'; + tabContentElementId = '<?= $block->escapeJs($block->getChildBlock('diagrams')->getId()) ?>_<?= $block->escapeJs($tabId) ?>_content'; try { if (transport.responseText.isJSON()) { var response = transport.responseText.evalJSON() @@ -44,8 +41,8 @@ window.changeDiagramsPeriod = function(periodObj) { } } }); -<?php endforeach; ?> - ajaxBlockUrl = '<?= $block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => 'totals', 'period' => '']) ?>' + periodParam; + <?php endforeach; ?> + ajaxBlockUrl = '<?= $block->escapeJs($block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => 'totals', 'period' => ''])) ?>' + periodParam; new Ajax.Request(ajaxBlockUrl, { parameters: {isAjax: 'true', form_key: FORM_KEY}, onSuccess: function(transport) { @@ -93,15 +90,15 @@ window.changeDiagramsPeriod = function(periodObj) { <div class="dashboard-secondary col-m-4 col-m-pull-8"> <?= $block->getChildHtml('sales') ?> <div class="dashboard-item"> - <div class="dashboard-item-title"><?= /* @escapeNotVerified */ __('Last Orders') ?></div> + <div class="dashboard-item-title"><?= $block->escapeHtml(__('Last Orders')) ?></div> <?= $block->getChildHtml('lastOrders') ?> </div> <div class="dashboard-item"> - <div class="dashboard-item-title"><?= /* @escapeNotVerified */ __('Last Search Terms') ?></div> + <div class="dashboard-item-title"><?= $block->escapeHtml(__('Last Search Terms')) ?></div> <?= $block->getChildHtml('lastSearches') ?> </div> <div class="dashboard-item"> - <div class="dashboard-item-title"><?= /* @escapeNotVerified */ __('Top Search Terms') ?></div> + <div class="dashboard-item-title"><?= $block->escapeHtml(__('Top Search Terms')) ?></div> <?= $block->getChildHtml('topSearches') ?> </div> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/salebar.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/salebar.phtml index 450a2c89b50da..139a7cad4185f 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/salebar.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/salebar.phtml @@ -3,18 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if (sizeof($block->getTotals()) > 0): ?> - <?php foreach ($block->getTotals() as $_total): ?> +<?php if (count($block->getTotals()) > 0) : ?> + <?php foreach ($block->getTotals() as $_total) : ?> <div class="dashboard-item dashboard-item-primary"> - <div class="dashboard-item-title"><?= /* @escapeNotVerified */ $_total['label'] ?></div> + <div class="dashboard-item-title"><?= $block->escapeHtml($_total['label']) ?></div> <div class="dashboard-item-content"> <strong class="dashboard-sales-value"> - <?= /* @escapeNotVerified */ $_total['value'] ?> - <span class="dashboard-sales-decimals"><?= /* @escapeNotVerified */ $_total['decimals'] ?></span> + <?= /* @noEscape */ $_total['value'] ?> + <span class="dashboard-sales-decimals"><?= /* @noEscape */ $_total['decimals'] ?></span> </strong> </div> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/searches.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/searches.phtml index f6e837fd54ede..7a7a71f07fa55 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/searches.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/searches.phtml @@ -3,16 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if (count($block->getCollection()->getItems()) > 0): ?> +<?php if (count($block->getCollection()->getItems()) > 0) : ?> <div class="searches-results"> - <?php foreach ($block->getCollection()->getItems() as $item): ?> - <span><?= /* @escapeNotVerified */ $item->getQueryText() ?></span><br /> + <?php foreach ($block->getCollection()->getItems() as $item) : ?> + <span><?= $block->escapeHtml($item->getQueryText()) ?></span><br /> <?php endforeach; ?> </div> -<?php else: ?> - <div class="empty-text"><?= /* @escapeNotVerified */ __('There are no search keywords.') ?></div> +<?php else : ?> + <div class="empty-text"><?= $block->escapeHtml(__('There are no search keywords.')) ?></div> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/store/switcher.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/store/switcher.phtml index bf9ae27f17b48..87e5399ddda44 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/store/switcher.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/store/switcher.phtml @@ -3,31 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<p class="switcher"><label for="store_switcher"><?= /* @escapeNotVerified */ __('View Statistics For:') ?></label> +<p class="switcher"><label for="store_switcher"><?= $block->escapeHtml(__('View Statistics For:')) ?></label> <?= $block->getHintHtml() ?> <select name="store_switcher" id="store_switcher" class="left-col-block" onchange="return switchStore(this);"> - <option value=""><?= /* @escapeNotVerified */ __('All Websites') ?></option> - <?php foreach ($block->getWebsiteCollection() as $_website): ?> + <option value=""><?= $block->escapeHtml(__('All Websites')) ?></option> + <?php foreach ($block->getWebsiteCollection() as $_website) : ?> <?php $showWebsite = false; ?> - <?php foreach ($block->getGroupCollection($_website) as $_group): ?> + <?php foreach ($block->getGroupCollection($_website) as $_group) : ?> <?php $showGroup = false; ?> - <?php foreach ($block->getStoreCollection($_group) as $_store): ?> - <?php if ($showWebsite == false): ?> + <?php foreach ($block->getStoreCollection($_group) as $_store) : ?> + <?php if ($showWebsite == false) : ?> <?php $showWebsite = true; ?> - <option website="true" value="<?= /* @escapeNotVerified */ $_website->getId() ?>"<?php if ($block->getRequest()->getParam('website') == $_website->getId()): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ $_website->getName() ?></option> + <option website="true" value="<?= $block->escapeHtmlAttr($_website->getId()) ?>"<?php if ($block->getRequest()->getParam('website') == $_website->getId()) : ?> selected="selected"<?php endif; ?>><?= $block->escapeHtml($_website->getName()) ?></option> <?php endif; ?> - <?php if ($showGroup == false): ?> + <?php if ($showGroup == false) : ?> <?php $showGroup = true; ?> - <!--optgroup label="   <?= /* @escapeNotVerified */ $_group->getName() ?>"--> - <option group="true" value="<?= /* @escapeNotVerified */ $_group->getId() ?>"<?php if ($block->getRequest()->getParam('group') == $_group->getId()): ?> selected="selected"<?php endif; ?>>   <?= /* @escapeNotVerified */ $_group->getName() ?></option> + <!--optgroup label="   <?= $block->escapeHtmlAttr($_group->getName()) ?>"--> + <option group="true" value="<?= $block->escapeHtmlAttr($_group->getId()) ?>"<?php if ($block->getRequest()->getParam('group') == $_group->getId()) : ?> selected="selected"<?php endif; ?>>   <?= $block->escapeHtml($_group->getName()) ?></option> <?php endif; ?> - <option value="<?= /* @escapeNotVerified */ $_store->getId() ?>"<?php if ($block->getStoreId() == $_store->getId()): ?> selected="selected"<?php endif; ?>>      <?= /* @escapeNotVerified */ $_store->getName() ?></option> + <option value="<?= $block->escapeHtmlAttr($_store->getId()) ?>"<?php if ($block->getStoreId() == $_store->getId()) : ?> selected="selected"<?php endif; ?>>      <?= $block->escapeHtml($_store->getName()) ?></option> <?php endforeach; ?> - <?php if ($showGroup): ?> + <?php if ($showGroup) : ?> <!--</optgroup>--> <?php endif; ?> <?php endforeach; ?> @@ -57,7 +54,7 @@ var select = $('order_amounts_period'); } var periodParam = select.value ? 'period/' + select.value + '/' : ''; - setLocation('<?= /* @escapeNotVerified */ $block->getSwitchUrl() ?>' + storeParam + periodParam); + setLocation('<?= $block->escapeJs($block->getSwitchUrl()) ?>' + storeParam + periodParam); } }); </script> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml index 8605304b1f528..918eea75fab99 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml @@ -3,19 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if (sizeof($block->getTotals()) > 0): ?> +<?php if (count($block->getTotals()) > 0) : ?> <div class="dashboard-totals" id="dashboard_diagram_totals"> <ul class="dashboard-totals-list"> - <?php foreach ($block->getTotals() as $_total): ?> + <?php foreach ($block->getTotals() as $_total) : ?> <li class="dashboard-totals-item"> - <span class="dashboard-totals-label"><?= /* @escapeNotVerified */ $_total['label'] ?></span> + <span class="dashboard-totals-label"><?= $block->escapeHtml($_total['label']) ?></span> <strong class="dashboard-totals-value"> - <?= /* @escapeNotVerified */ $_total['value'] ?> - <span class="dashboard-totals-decimals"><?= /* @escapeNotVerified */ $_total['decimals'] ?></span> + <?= /* @noEscape */ $_total['value'] ?> + <span class="dashboard-totals-decimals"><?= /* @noEscape */ $_total['decimals'] ?></span> </strong> </li> <?php endforeach; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml index 4d9ba6a8c4bad..83482b1720cf5 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml @@ -4,25 +4,23 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\Media\Uploader */ ?> <div id="<?= $block->getHtmlId() ?>" class="uploader" data-mage-init='{ "Magento_Backend/js/media-uploader" : { - "maxFileSize": <?= /* @escapeNotVerified */ $block->getFileSizeService()->getMaxFileSize() ?>, - "maxWidth": <?= /* @escapeNotVerified */ $block->getImageUploadMaxWidth() ?>, - "maxHeight": <?= /* @escapeNotVerified */ $block->getImageUploadMaxHeight() ?>, + "maxFileSize": <?= /* @noEscape */ $block->getFileSizeService()->getMaxFileSize() ?>, + "maxWidth": <?= /* @noEscape */ $block->getImageUploadMaxWidth() ?>, + "maxHeight": <?= /* @noEscape */ $block->getImageUploadMaxHeight() ?>, "isResizeEnabled": <?= /* @noEscape */ $block->getImageUploadConfigData()->getIsResizeEnabled() ?> } }' > <div class="fileinput-button form-buttons button"> - <span><?= /* @escapeNotVerified */ __('Browse Files...') ?></span> - <input id="fileupload" type="file" name="<?= /* @escapeNotVerified */ $block->getConfig()->getFileField() ?>" - data-url="<?= /* @escapeNotVerified */ $block->getConfig()->getUrl() ?>" multiple="multiple" /> + <span><?= $block->escapeHtml(__('Browse Files...')) ?></span> + <input id="fileupload" type="file" name="<?= $block->escapeHtmlAttr($block->getConfig()->getFileField()) ?>" + data-url="<?= $block->escapeHtmlAttr($block->getConfig()->getUrl()) ?>" multiple="multiple" /> </div> <div class="clear"></div> <script id="<?= $block->getHtmlId() ?>-template" type="text/x-magento-template" data-template="uploader"> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/menu.phtml b/app/code/Magento/Backend/view/adminhtml/templates/menu.phtml index c448bd61b0791..815cf9c8e4cd4 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/menu.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/menu.phtml @@ -3,12 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <nav data-mage-init='{"globalNavigation": {}}' class="admin__menu"> - <?= /* @escapeNotVerified */ $block->renderNavigation($block->getMenuModel(), 0, 12) ?> + <?= /* @noEscape */ $block->renderNavigation($block->getMenuModel(), 0, 12) ?> </nav> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/copyright.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/copyright.phtml index 55bdbb460ba07..e3a5c84ea4522 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/copyright.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/copyright.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<a class="link-copyright" href="http://magento.com" target="_blank" title="<?= /* @escapeNotVerified */ __('Magento') ?>"></a> -<?= /* @escapeNotVerified */ __('Copyright © %1 Magento Commerce Inc. All rights reserved.', date('Y')) ?> +<a class="link-copyright" href="http://magento.com" target="_blank" title="<?= $block->escapeHtmlAttr(__('Magento')) ?>"></a> +<?= $block->escapeHtml(__('Copyright © %1 Magento Commerce Inc. All rights reserved.', date('Y'))) ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/footer.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/footer.phtml index 78e2e6db15e91..3f21dcda9a544 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/footer.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/footer.phtml @@ -3,11 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <p class="magento-version"> - <strong><?= /* @escapeNotVerified */ __('Magento') ?></strong> - <?= /* @escapeNotVerified */ __('ver. %1', $block->getMagentoVersion()) ?> + <strong><?= $block->escapeHtml(__('Magento')) ?></strong> + <?= $block->escapeHtml(__('ver. %1', $block->getMagentoVersion())) ?> </p> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml index f952001f5e2ff..89f144664003d 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml @@ -4,26 +4,23 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\Page\Header */ +$part = $block->getShowPart(); ?> -<?php switch ($block->getShowPart()): - case 'logo': ?> +<?php if ($part === 'logo') : ?> <?php $edition = $block->hasEdition() ? 'data-edition="' . $block->escapeHtml($block->getEdition()) . '"' : ''; ?> <?php $logoSrc = ($block->hasLogoImageSrc()) ? $block->escapeHtml($block->getLogoImageSrc()) : 'images/magento-logo.svg' ?> <a - href="<?= /* @escapeNotVerified */ $block->getHomeLink() ?>" - <?= /* @escapeNotVerified */ $edition ?> + href="<?= $block->escapeUrl($block->getHomeLink()) ?>" + <?= /* @noEscape */ $edition ?> class="logo"> - <img class="logo-img" src="<?= /* @escapeNotVerified */ $block->getViewFileUrl($logoSrc) ?>" + <img class="logo-img" src="<?= /* @noEscape */ $block->getViewFileUrl($logoSrc) ?>" alt="<?= $block->escapeHtml(__('Magento Admin Panel')) ?>" title="<?= $block->escapeHtml(__('Magento Admin Panel')) ?>"/> </a> - <?php break; ?> - <?php case 'user': ?> +<?php elseif ($part === 'user') : ?> <div class="admin-user admin__action-dropdown-wrap"> <a - href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/system_account/index') ?>" + href="<?= /* @noEscape */ $block->getUrl('adminhtml/system_account/index') ?>" class="admin__action-dropdown" title="<?= $block->escapeHtml(__('My Account')) ?>" data-mage-init='{"dropdown":{}}' @@ -33,36 +30,35 @@ </span> </a> <ul class="admin__action-dropdown-menu"> - <?php if ($block->getAuthorization()->isAllowed('Magento_Backend::myaccount')): ?> + <?php if ($block->getAuthorization()->isAllowed('Magento_Backend::myaccount')) : ?> <li> <a - href="<?= /* @escapeNotVerified */ $block->getUrl('adminhtml/system_account/index') ?>" - <?= /* @escapeNotVerified */ $block->getUiId('user', 'account', 'settings') ?> + href="<?= /* @noEscape */ $block->getUrl('adminhtml/system_account/index') ?>" + <?= /* @noEscape */ $block->getUiId('user', 'account', 'settings') ?> title="<?= $block->escapeHtml(__('Account Setting')) ?>"> - <?= /* @escapeNotVerified */ __('Account Setting') ?> (<span class="admin-user-name"><?= $block->escapeHtml($block->getUser()->getUserName()) ?></span>) + <?= $block->escapeHtml(__('Account Setting')) ?> (<span class="admin-user-name"><?= $block->escapeHtml($block->getUser()->getUserName()) ?></span>) </a> </li> <?php endif; ?> <li> <a - href="<?= /* @escapeNotVerified */ $block->getBaseUrl() ?>" + href="<?= /* @noEscape */ $block->getBaseUrl() ?>" title="<?= $block->escapeHtml(__('Customer View')) ?>" target="_blank" class="store-front"> - <?= /* @escapeNotVerified */ __('Customer View') ?> + <?= $block->escapeHtml(__('Customer View')) ?> </a> </li> <li> <a - href="<?= /* @escapeNotVerified */ $block->getLogoutLink() ?>" + href="<?= /* @noEscape */ $block->getLogoutLink() ?>" class="account-signout" title="<?= $block->escapeHtml(__('Sign Out')) ?>"> - <?= /* @escapeNotVerified */ __('Sign Out') ?> + <?= $block->escapeHtml(__('Sign Out')) ?> </a> </li> </ul> </div> - <?php break; ?> - <?php case 'other': ?> - <?= $block->getChildHtml() ?> - <?php break; ?> -<?php endswitch; ?> + +<?php elseif ($part === 'other') : ?> + <?= $block->getChildHtml() ?> +<?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/js/calendar.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/js/calendar.phtml index e47bf5830f9ee..94df9ef9eb872 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/js/calendar.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/js/calendar.phtml @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// no notice of license for now ?> <?php @@ -22,20 +21,20 @@ require([ $.extend(true, $, { calendarConfig: { - dayNames: <?= /* @escapeNotVerified */ $days['wide'] ?>, - dayNamesMin: <?= /* @escapeNotVerified */ $days['abbreviated'] ?>, - monthNames: <?= /* @escapeNotVerified */ $months['wide'] ?>, - monthNamesShort: <?= /* @escapeNotVerified */ $months['abbreviated'] ?>, - infoTitle: "<?= /* @escapeNotVerified */ __('About the calendar') ?>", - firstDay: <?= /* @escapeNotVerified */ $firstDay ?>, - closeText: "<?= /* @escapeNotVerified */ __('Close') ?>", - currentText: "<?= /* @escapeNotVerified */ __('Go Today') ?>", - prevText: "<?= /* @escapeNotVerified */ __('Previous') ?>", - nextText: "<?= /* @escapeNotVerified */ __('Next') ?>", - weekHeader: "<?= /* @escapeNotVerified */ __('WK') ?>", - timeText: "<?= /* @escapeNotVerified */ __('Time') ?>", - hourText: "<?= /* @escapeNotVerified */ __('Hour') ?>", - minuteText: "<?= /* @escapeNotVerified */ __('Minute') ?>", + dayNames: <?= /* @noEscape */ $days['wide'] ?>, + dayNamesMin: <?= /* @noEscape */ $days['abbreviated'] ?>, + monthNames: <?= /* @noEscape */ $months['wide'] ?>, + monthNamesShort: <?= /* @noEscape */ $months['abbreviated'] ?>, + infoTitle: "<?= $block->escapeJs(__('About the calendar')) ?>", + firstDay: <?= /* @noEscape */ $firstDay ?>, + closeText: "<?= $block->escapeJs(__('Close')) ?>", + currentText: "<?= $block->escapeJs(__('Go Today')) ?>", + prevText: "<?= $block->escapeJs(__('Previous')) ?>", + nextText: "<?= $block->escapeJs(__('Next')) ?>", + weekHeader: "<?= $block->escapeJs(__('WK')) ?>", + timeText: "<?= $block->escapeJs(__('Time')) ?>", + hourText: "<?= $block->escapeJs(__('Hour')) ?>", + minuteText: "<?= $block->escapeJs(__('Minute')) ?>", dateFormat: $.datepicker.RFC_2822, showOn: "button", showAnim: "", @@ -52,11 +51,11 @@ require([ showMinute: false, serverTimezoneSeconds: <?= (int) $block->getStoreTimestamp() ?>, serverTimezoneOffset: <?= (int) $block->getTimezoneOffsetSeconds() ?>, - yearRange: '<?= /* @escapeNotVerified */ $block->getYearRange() ?>' + yearRange: '<?= $block->escapeJs($block->getYearRange()) ?>' } }); -enUS = <?= /* @escapeNotVerified */ $enUS ?>; // en_US locale reference +enUS = <?= /* @noEscape */ $enUS ?>; // en_US locale reference }); </script> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/js/components.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/js/components.phtml index c6c7bcc901e7e..5277a1df2f31e 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/js/components.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/js/components.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/js/require_js.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/js/require_js.phtml index 9cb2cec091939..68453d9ff8ff2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/js/require_js.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/js/require_js.phtml @@ -5,9 +5,9 @@ */ ?> <script> - var BASE_URL = '<?= /* @escapeNotVerified */ $block->getUrl('*') ?>'; - var FORM_KEY = '<?= /* @escapeNotVerified */ $block->getFormKey() ?>'; + var BASE_URL = '<?= /* @noEscape */ $block->getUrl('*') ?>'; + var FORM_KEY = '<?= /* @noEscape */ $block->getFormKey() ?>'; var require = { - "baseUrl": "<?= /* @escapeNotVerified */ $block->getViewFileUrl('/') ?>" + "baseUrl": "<?= /* @noEscape */ $block->getViewFileUrl('/') ?>" }; </script> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml index 5418ad58b9519..93df0aec94ef1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/notices.phtml @@ -3,29 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** * @see \Magento\Backend\Block\Page\Notices */ ?> -<?php if ($block->displayNoscriptNotice()): ?> +<?php if ($block->displayNoscriptNotice()) : ?> <noscript> <div class="messages"> <div class="message message-warning message-noscript"> - <strong><?= /* @escapeNotVerified */ __('JavaScript may be disabled in your browser.') ?></strong> - <?= /* @escapeNotVerified */ __('To use this website you must first enable JavaScript in your browser.') ?> + <strong><?= $block->escapeHtml(__('JavaScript may be disabled in your browser.')) ?></strong> + <?= $block->escapeHtml(__('To use this website you must first enable JavaScript in your browser.')) ?> </div> </div> </noscript> <?php endif; ?> -<?php if ($block->displayDemoNotice()): ?> +<?php if ($block->displayDemoNotice()) : ?> <div class="messages"> <div class="message message-warning message-demo-mode"> - <?= /* @escapeNotVerified */ __('This is only a demo store. You can browse and place orders, but nothing will be processed.') ?> + <?= $block->escapeHtml(__('This is only a demo store. You can browse and place orders, but nothing will be processed.')) ?> </div> </div> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/privacyPolicy.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/privacyPolicy.phtml new file mode 100644 index 0000000000000..8c8767627839b --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/privacyPolicy.phtml @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +?> + +<a class="link-report" href="<?= $block->escapeUrl($block->getPrivacypolicyUrl()) ?>" id="footer_privacy" target="_blank"> + <?= $block->escapeHtml(__('Privacy Policy')) ?> +</a> | \ No newline at end of file diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml index 4ef6d378cc4a4..2965983e12150 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/report.phtml @@ -3,12 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if ($block->getBugreportUrl()): ?> - <a class="link-report" href="<?= /* @escapeNotVerified */ $block->getBugreportUrl() ?>" id="footer_bug_tracking" target="_blank"> - <?= /* @escapeNotVerified */ __('Report an Issue') ?> +<?php if ($block->getBugreportUrl()) : ?> + <a class="link-report" href="<?= $block->escapeUrl($block->getBugreportUrl()) ?>" id="footer_bug_tracking" target="_blank"> + <?= $block->escapeHtml(__('Report an Issue')) ?> </a> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml index 0a1dcb0b626e6..56a8161b57e0b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml @@ -3,12 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if ($block->getChildHtml()):?> - <div data-mage-init='{"floatingHeader": {}}' class="page-actions floating-header" <?= /* @escapeNotVerified */ $block->getUiId('content-header') ?>> +<?php if ($block->getChildHtml()) :?> + <div data-mage-init='{"floatingHeader": {}}' class="page-actions floating-header" <?= /* @noEscape */ $block->getUiId('content-header') ?>> <?= $block->getChildHtml() ?> </div> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml index bb968c57610be..da18bc183759b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml @@ -4,27 +4,25 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\Backend\Block\Store\Switcher */ ?> -<?php if ($websites = $block->getWebsites()): ?> +<?php if ($websites = $block->getWebsites()) : ?> <div class="store-switcher store-view"> - <span class="store-switcher-label"><?= /* @escapeNotVerified */ __('Store View:') ?></span> + <span class="store-switcher-label"><?= $block->escapeHtml(__('Store View:')) ?></span> <div class="actions dropdown closable"> <input type="hidden" name="store_switcher" id="store_switcher" - data-role="store-view-id" data-param="<?= /* @escapeNotVerified */ $block->getStoreVarName() ?>" + data-role="store-view-id" data-param="<?= $block->escapeHtmlAttr($block->getStoreVarName()) ?>" value="<?= $block->escapeHtml($block->getStoreId()) ?>" - onchange="switchScope(this);"<?= /* @escapeNotVerified */ $block->getUiId() ?> /> + onchange="switchScope(this);"<?= /* @noEscape */ $block->getUiId() ?> /> <input type="hidden" name="store_group_switcher" id="store_group_switcher" - data-role="store-group-id" data-param="<?= /* @escapeNotVerified */ $block->getStoreGroupVarName() ?>" + data-role="store-group-id" data-param="<?= $block->escapeHtmlAttr($block->getStoreGroupVarName()) ?>" value="<?= $block->escapeHtml($block->getStoreGroupId()) ?>" - onchange="switchScope(this);"<?= /* @escapeNotVerified */ $block->getUiId() ?> /> + onchange="switchScope(this);"<?= /* @noEscape */ $block->getUiId() ?> /> <input type="hidden" name="website_switcher" id="website_switcher" - data-role="website-id" data-param="<?= /* @escapeNotVerified */ $block->getWebsiteVarName() ?>" + data-role="website-id" data-param="<?= $block->escapeHtmlAttr($block->getWebsiteVarName()) ?>" value="<?= $block->escapeHtml($block->getWebsiteId()) ?>" - onchange="switchScope(this);"<?= /* @escapeNotVerified */ $block->getUiId() ?> /> + onchange="switchScope(this);"<?= /* @noEscape */ $block->getUiId() ?> /> <button type="button" class="admin__action-dropdown" @@ -32,96 +30,64 @@ data-toggle="dropdown" aria-haspopup="true" id="store-change-button"> - <?= /* @escapeNotVerified */ $block->getCurrentSelectionName() ?> + <?= $block->escapeHtml($block->getCurrentSelectionName()) ?> </button> <ul class="dropdown-menu" data-role="stores-list"> - <?php if ($block->hasDefaultOption()): ?> - <li class="store-switcher-all <?php if ( ! ($block->getDefaultSelectionName() != $block->getCurrentSelectionName())) { - echo "disabled"; - } ?> <?php if ( ! $block->hasScopeSelected()) { - ?> current<?php - } ?>"> - <?php if ($block->getDefaultSelectionName() != $block->getCurrentSelectionName()) { - ?> + <?php if ($block->hasDefaultOption()) : ?> + <li class="store-switcher-all <?php if (!($block->getDefaultSelectionName() != $block->getCurrentSelectionName())) : ?>disabled<?php endif; ?> <?php if (!$block->hasScopeSelected()) : ?>current<?php endif; ?>"> + <?php if ($block->getDefaultSelectionName() != $block->getCurrentSelectionName()) : ?> <a data-role="store-view-id" data-value="" href="#"> - <?= /* @escapeNotVerified */ $block->getDefaultSelectionName() ?> + <?= $block->escapeHtml($block->getDefaultSelectionName()) ?> </a> - <?php - } else { - ?> - <span><?= /* @escapeNotVerified */ $block->getDefaultSelectionName() ?></span> - <?php - } ?> + <?php else : ?> + <span><?= $block->escapeHtml($block->getDefaultSelectionName()) ?></span> + <?php endif; ?> </li> <?php endif; ?> - <?php foreach ($websites as $website): ?> + <?php foreach ($websites as $website) : ?> <?php $showWebsite = false; ?> - <?php foreach ($website->getGroups() as $group): ?> + <?php foreach ($website->getGroups() as $group) : ?> <?php $showGroup = false; ?> - <?php foreach ($block->getStores($group) as $store): ?> - <?php if ($showWebsite == false): ?> + <?php foreach ($block->getStores($group) as $store) : ?> + <?php if ($showWebsite == false) : ?> <?php $showWebsite = true; ?> - <li class="store-switcher-website <?php if ( ! ($block->isWebsiteSwitchEnabled() && ! $block->isWebsiteSelected($website))) { - echo "disabled"; - } ?> <?php if ($block->isWebsiteSelected($website)) { - ?> current<?php - } ?>"> - <?php if ($block->isWebsiteSwitchEnabled() && ! $block->isWebsiteSelected($website)) { - ?> + <li class="store-switcher-website <?php if (!($block->isWebsiteSwitchEnabled() && ! $block->isWebsiteSelected($website))) : ?>disabled<?php endif; ?> <?php if ($block->isWebsiteSelected($website)) : ?>current<?php endif; ?>"> + <?php if ($block->isWebsiteSwitchEnabled() && ! $block->isWebsiteSelected($website)) : ?> <a data-role="website-id" data-value="<?= $block->escapeHtml($website->getId()) ?>" href="#"> <?= $block->escapeHtml($website->getName()) ?> </a> - <?php - } else { - ?> + <?php else : ?> <span><?= $block->escapeHtml($website->getName()) ?></span> - <?php - } ?> + <?php endif; ?> </li> <?php endif; ?> - <?php if ($showGroup == false): ?> + <?php if ($showGroup == false) : ?> <?php $showGroup = true; ?> - <li class="store-switcher-store <?php if ( ! ($block->isStoreGroupSwitchEnabled() && ! $block->isStoreGroupSelected($group))) { - echo "disabled"; - } ?> <?php if ($block->isStoreGroupSelected($group)) { - ?> current<?php - } ?>"> - <?php if ($block->isStoreGroupSwitchEnabled() && ! $block->isStoreGroupSelected($group)) { - ?> + <li class="store-switcher-store <?php if (!($block->isStoreGroupSwitchEnabled() && ! $block->isStoreGroupSelected($group))) : ?>disabled<?php endif; ?> <?php if ($block->isStoreGroupSelected($group)) : ?>current<?php endif; ?>"> + <?php if ($block->isStoreGroupSwitchEnabled() && ! $block->isStoreGroupSelected($group)) : ?> <a data-role="store-group-id" data-value="<?= $block->escapeHtml($group->getId()) ?>" href="#"> <?= $block->escapeHtml($group->getName()) ?> </a> - <?php - } else { - ?> + <?php else : ?> <span><?= $block->escapeHtml($group->getName()) ?></span> - <?php - } ?> + <?php endif; ?> </li> <?php endif; ?> - <li class="store-switcher-store-view <?php if ( ! ($block->isStoreSwitchEnabled() && ! $block->isStoreSelected($store))) { - echo "disabled"; - } ?> <?php if ($block->isStoreSelected($store)) { - ?> current<?php - } ?>"> - <?php if ($block->isStoreSwitchEnabled() && ! $block->isStoreSelected($store)) { - ?> + <li class="store-switcher-store-view <?php if (!($block->isStoreSwitchEnabled() && !$block->isStoreSelected($store))) : ?>disabled<?php endif; ?> <?php if ($block->isStoreSelected($store)) :?>current<?php endif; ?>"> + <?php if ($block->isStoreSwitchEnabled() && ! $block->isStoreSelected($store)) : ?> <a data-role="store-view-id" data-value="<?= $block->escapeHtml($store->getId()) ?>" href="#"> <?= $block->escapeHtml($store->getName()) ?> </a> - <?php - } else { - ?> + <?php else : ?> <span><?= $block->escapeHtml($store->getName()) ?></span> - <?php - } ?> + <?php endif; ?> </li> <?php endforeach; ?> <?php endforeach; ?> <?php endforeach; ?> - <?php if ($block->getShowManageStoresLink() && $block->getAuthorization()->isAllowed('Magento_Backend::store')): ?> + <?php if ($block->getShowManageStoresLink() && $block->getAuthorization()->isAllowed('Magento_Backend::store')) : ?> <li class="dropdown-toolbar"> - <a href="<?= /* @escapeNotVerified */ $block->getUrl('*/system_store') ?>"><?= /* @escapeNotVerified */ __('Stores Configuration') ?></a> + <a href="<?= /* @noEscape */ $block->getUrl('*/system_store') ?>"><?= $block->escapeHtml(__('Stores Configuration')) ?></a> </li> <?php endif; ?> </ul> @@ -173,10 +139,10 @@ require([ scopeSwitcherHandler(switcherParams); } else { - <?php if ($block->getUseConfirm()): ?> + <?php if ($block->getUseConfirm()) : ?> confirm({ - content: "<?= /* @escapeNotVerified */ __('Please confirm scope switching. All data that hasn\'t been saved will be lost.') ?>", + content: "<?= $block->escapeJs(__('Please confirm scope switching. All data that hasn\'t been saved will be lost.')) ?>", actions: { confirm: function() { reload(); @@ -187,16 +153,16 @@ require([ } }); - <?php else: ?> + <?php else : ?> reload(); <?php endif; ?> } function reload() { - <?php if (!$block->isUsingIframe()): ?> - var url = '<?= /* @escapeNotVerified */ $block->getSwitchUrl() ?>' + scopeParams; + <?php if (!$block->isUsingIframe()) : ?> + var url = '<?= $block->escapeJs($block->getSwitchUrl()) ?>' + scopeParams; setLocation(url); - <?php else: ?> + <?php else : ?> jQuery('#preview_selected_store').val(scopeId); jQuery('#preview_form').submit(); diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml index 49a2c681285bf..382fb6e81c519 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml @@ -3,43 +3,40 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_element = $block->getElement() ?> -<?php if ($_element->getFieldsetContainerId()): ?> - <div id="<?= /* @escapeNotVerified */ $_element->getFieldsetContainerId() ?>">789 +<?php if ($_element->getFieldsetContainerId()) : ?> + <div id="<?= $block->escapeHtmlAttr($_element->getFieldsetContainerId()) ?>">789 <?php endif; ?> -<?php if (!$_element->getNoContainer()): ?> - <fieldset class="admin__fieldset fieldset <?= /* @escapeNotVerified */ $_element->getClass() ?>" id="<?= $_element->getHtmlId() ?>"> +<?php if (!$_element->getNoContainer()) : ?> + <fieldset class="admin__fieldset fieldset <?= $block->escapeHtmlAttr($_element->getClass()) ?>" id="<?= $_element->getHtmlId() ?>"> <?php endif; ?> - <?php if ($_element->getLegend()): ?> + <?php if ($_element->getLegend()) : ?> <legend class="admin__legend legend"> - <span><?= /* @escapeNotVerified */ $_element->getLegend() ?></span> + <span><?= $block->escapeHtml($_element->getLegend()) ?></span> <?= $block->getHintHtml() ?> </legend><br/> - <?= /* @escapeNotVerified */ $_element->getHeaderBar() ?> - <?php else: ?> + <?= /* @noEscape */ $_element->getHeaderBar() ?> + <?php else : ?> <?= $block->getHintHtml() ?> <?php endif; ?> <div class="admin__fieldset tree-store-scope"> - <?php if ($_element->getComment()): ?> + <?php if ($_element->getComment()) : ?> <p class="comment"><?= $block->escapeHtml($_element->getComment()) ?></p> <?php endif; ?> - <?php if ($_element->hasHtmlContent()): ?> - <?= $_element->getHtmlContent() ?> - <?php else: ?> + <?php if ($_element->hasHtmlContent()) : ?> + <?= $_element->getHtmlContent() ?> + <?php else : ?> <?= $_element->getChildrenHtml() ?> <?php endif; ?> </div> <?= $_element->getSubFieldsetHtml() ?> -<?php if (!$_element->getNoContainer()): ?> +<?php if (!$_element->getNoContainer()) : ?> </fieldset> <?php endif; ?> -<?php if ($_element->getFieldsetContainerId()): ?> +<?php if ($_element->getFieldsetContainerId()) : ?> </div> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml index e25c9d22ca40a..959a27279e5c2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element */ @@ -24,21 +21,21 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' . $block->getUiId('form-field', $element->getId()); ?> -<?php if (!$element->getNoDisplay()): ?> - <?php if ($element->getType() == 'hidden'): ?> +<?php if (!$element->getNoDisplay()) : ?> + <?php if ($element->getType() == 'hidden') : ?> <?= $element->getElementHtml() ?> - <?php else: ?> - <div<?= /* @escapeNotVerified */ $fieldAttributes ?>> - <?php if ($elementBeforeLabel): ?> + <?php else : ?> + <div<?= /* @noEscape */ $fieldAttributes ?>> + <?php if ($elementBeforeLabel) : ?> <?= $element->getElementHtml() ?> <?= $element->getLabelHtml('', $element->getScopeLabel()) ?> - <?= /* @escapeNotVerified */ $note ?> - <?php else: ?> + <?= /* @noEscape */ $note ?> + <?php else : ?> <?= $element->getLabelHtml('', $element->getScopeLabel()) ?> <div class="admin__field-control control"> - <?= /* @escapeNotVerified */ ($addOn) ? '<div class="addon">' . $element->getElementHtml() . '</div>' : $element->getElementHtml() ?> + <?= /* @noEscape */ ($addOn) ? '<div class="addon">' . $element->getElementHtml() . '</div>' : $element->getElementHtml() ?> <?= $block->getHintHtml() ?> - <?= /* @escapeNotVerified */ $note ?> + <?= /* @noEscape */ $note ?> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/autocomplete.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/autocomplete.phtml index 22d93241f43f2..7ac867970e820 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/autocomplete.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/autocomplete.phtml @@ -3,15 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <ul class="dropdown-menu"> - <?php foreach ($items as $item): ?> - <li id="<?= /* @escapeNotVerified */ $item['id'] ?>" class="item"> - <a href="<?= /* @escapeNotVerified */ $item['url'] ?>" class="title"><?= $block->escapeHtml($item['name']) ?></a> - <div class="type"><?= /* @escapeNotVerified */ $item['type'] ?></div> + <?php foreach ($items as $item) : ?> + <li id="<?= $block->escapeHtmlAttr($item['id']) ?>" class="item"> + <a href="<?= $block->escapeUrl($item['url']) ?>" class="title"><?= $block->escapeHtml($item['name']) ?></a> + <div class="type"><?= $block->escapeHtml($item['type']) ?></div> <?= $block->escapeHtml($item['description']) ?> </li> <?php endforeach ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml index b4bc42b95d0aa..c392ebf3883d2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml @@ -4,17 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Backend\Block\Cache\Permissions|null $permissions */ $permissions = $block->getData('permissions'); ?> -<?php if ($permissions && $permissions->hasAccessToAdditionalActions()): ?> +<?php if ($permissions && $permissions->hasAccessToAdditionalActions()) : ?> <div class="additional-cache-management"> <h2> <span><?= $block->escapeHtml(__('Additional Cache Management')); ?></span> </h2> - <?php if ($permissions->hasAccessToFlushCatalogImages()): ?> + <?php if ($permissions->hasAccessToFlushCatalogImages()) : ?> <p> <button onclick="setLocation('<?= $block->escapeJs($block->getCleanImagesUrl()); ?>')" type="button"> <?= $block->escapeHtml(__('Flush Catalog Images Cache')); ?> @@ -22,7 +20,7 @@ $permissions = $block->getData('permissions'); <span><?= $block->escapeHtml(__('Pregenerated product images files')); ?></span> </p> <?php endif; ?> - <?php if ($permissions->hasAccessToFlushJsCss()): ?> + <?php if ($permissions->hasAccessToFlushJsCss()) : ?> <p> <button onclick="setLocation('<?= $block->escapeJs($block->getCleanMediaUrl()); ?>')" type="button"> <?= $block->escapeHtml(__('Flush JavaScript/CSS Cache')); ?> @@ -30,7 +28,7 @@ $permissions = $block->getData('permissions'); <span><?= $block->escapeHtml(__('Themes JavaScript and CSS files combined to one file')) ?></span> </p> <?php endif; ?> - <?php if (!$block->isInProductionMode() && $permissions->hasAccessToFlushStaticFiles()): ?> + <?php if (!$block->isInProductionMode() && $permissions->hasAccessToFlushStaticFiles()) : ?> <p> <button onclick="setLocation('<?= $block->escapeJs($block->getCleanStaticFilesUrl()); ?>')" type="button"> <?= $block->escapeHtml(__('Flush Static Files Cache')); ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/edit.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/edit.phtml index 8e52f245f74d5..d1c51f0755a72 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/edit.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/edit.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -17,7 +14,7 @@ */ ?> <div data-mage-init='{"floatingHeader": {}}' class="page-actions"><?= $block->getSaveButtonHtml() ?></div> -<form action="<?= /* @escapeNotVerified */ $block->getSaveUrl() ?>" method="post" id="config-edit-form" enctype="multipart/form-data"> +<form action="<?= $block->escapeUrl($block->getSaveUrl()) ?>" method="post" id="config-edit-form" enctype="multipart/form-data"> <?= $block->getBlockHtml('formkey') ?> <script> @@ -34,26 +31,30 @@ <?= $block->getChildHtml('form') ?> <div class="entry-edit"> <div class="entry-edit-head"> - <h4 class="icon-head head-edit-form fieldset-legend"><?= /* @escapeNotVerified */ __('Catalog') ?></h4> + <h4 class="icon-head head-edit-form fieldset-legend"><?= $block->escapeHtml(__('Catalog')) ?></h4> </div> <fieldset id="catalog"> <table class="form-list"> <tbody> - <?php foreach ($block->getCatalogData() as $_item): ?> - <?php /* disable reindex buttons. functionality moved to index management*/?> - <?php if ($_item['buttons'][0]['name'] != 'clear_images_cache') { - continue; -}?> + <?php foreach ($block->getCatalogData() as $_item) : ?> + <?php /* disable reindex buttons. functionality moved to index management*/?> + <?php + if ($_item['buttons'][0]['name'] != 'clear_images_cache') { + continue; + } + ?> <tr> - <td class="label"><label><?= /* @escapeNotVerified */ $_item['label'] ?></label></td> + <td class="label"><label><?= $block->escapeHtml($_item['label']) ?></label></td> <td class="value"> - <?php foreach ($_item['buttons'] as $_button): ?> + <?php foreach ($_item['buttons'] as $_button) : ?> <?php $clickAction = "setCacheAction('catalog_action',this)"; ?> - <?php if (isset($_button['warning']) && $_button['warning']): ?> + <?php if (isset($_button['warning']) && $_button['warning']) : ?> + <?php //phpcs:disable ?> <?php $clickAction = "if (confirm('" . addslashes($_button['warning']) . "')) {{$clickAction}}"; ?> + <?php //phpcs:enable ?> <?php endif; ?> - <button <?php if (!isset($_button['disabled']) || !$_button['disabled']):?>onclick="<?= /* @escapeNotVerified */ $clickAction ?>"<?php endif; ?> id="<?= /* @escapeNotVerified */ $_button['name'] ?>" type="button" class="scalable <?php if (isset($_button['disabled']) && $_button['disabled']):?>disabled<?php endif; ?>" style=""><span><span><span><?= /* @escapeNotVerified */ $_button['action'] ?></span></span></span></button> - <?php if (isset($_button['comment'])): ?> <br /> <small><?= /* @escapeNotVerified */ $_button['comment'] ?></small> <?php endif; ?> + <button <?php if (!isset($_button['disabled']) || !$_button['disabled']) :?>onclick="<?= /* @noEscape */ $clickAction ?>"<?php endif; ?> id="<?= $block->escapeHtmlAttr($_button['name']) ?>" type="button" class="scalable <?php if (isset($_button['disabled']) && $_button['disabled']) :?>disabled<?php endif; ?>" style=""><span><span><span><?= $block->escapeHtml($_button['action']) ?></span></span></span></button> + <?php if (isset($_button['comment'])) : ?> <br /> <small><?= $block->escapeHtml($_button['comment']) ?></small> <?php endif; ?> <?php endforeach; ?> </td> <td><small> </small></td> @@ -66,16 +67,16 @@ <div class="entry-edit"> <div class="entry-edit-head"> - <h4 class="icon-head head-edit-form fieldset-legend"><?= /* @escapeNotVerified */ __('JavaScript/CSS') ?></h4> + <h4 class="icon-head head-edit-form fieldset-legend"><?= $block->escapeHtml(__('JavaScript/CSS')) ?></h4> </div> <fieldset id="jscss"> <table class="form-list"> <tbody> <tr> - <td class="label"><label><?= /* @escapeNotVerified */ __('JavaScript/CSS Cache') ?></label></td> + <td class="label"><label><?= $block->escapeHtml(__('JavaScript/CSS Cache')) ?></label></td> <td class="value"> - <button onclick="setCacheAction('jscss_action', this)" id='jscss_action' type="button" class="scalable"><span><span><span><?= /* @escapeNotVerified */ __('Clear') ?></span></span></span></button> + <button onclick="setCacheAction('jscss_action', this)" id='jscss_action' type="button" class="scalable"><span><span><span><?= $block->escapeHtml(__('Clear')) ?></span></span></span></button> </td> </tr> </tbody> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/design/edit.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/design/edit.phtml index 6b2f932cac7bf..c9cd765de35be 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/design/edit.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/design/edit.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ ?> -<form action="<?= /* @escapeNotVerified */ $block->getSaveUrl() ?>" method="post" id="design-edit-form"> +<form action="<?= $block->escapeUrl($block->getSaveUrl()) ?>" method="post" id="design-edit-form"> <?= $block->getBlockHtml('formkey') ?> </form> <script> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml index 902c6932f0ae1..c0928f4723b50 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/design/index.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml('grid') ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml index af369800287c1..6e94770c6e408 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\GlobalSearch */ ?> <div class="search-global" data-mage-init='{"globalSearch": {}}'> @@ -17,27 +15,29 @@ class="search-global-input" id="search-global" name="query" - data-mage-init='<?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getWidgetInitOptions()) ?>'> + <?php //phpcs:disable ?> + data-mage-init='<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getWidgetInitOptions()) ?>'> + <?php //phpcs:enable ?> <button type="submit" class="search-global-action" - title="<?= /* @escapeNotVerified */ __('Search') ?>" + title="<?= $block->escapeHtmlAttr(__('Search')) ?>" ></button> </div> </form> <script data-template="search-suggest" type="text/x-magento-template"> <ul class="search-global-menu"> <li class="item"> - <a id="searchPreviewProducts" href="<?= /* @escapeNotVerified */ $block->getUrl('catalog/product/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Products</a> + <a id="searchPreviewProducts" href="<?= $block->escapeUrl($block->getUrl('catalog/product/index/')) ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Products</a> </li> <li class="item"> - <a id="searchPreviewOrders" href="<?= /* @escapeNotVerified */ $block->getUrl('sales/order/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Orders</a> + <a id="searchPreviewOrders" href="<?= $block->escapeUrl($block->getUrl('sales/order/index/')) ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Orders</a> </li> <li class="item"> - <a id="searchPreviewCustomers" href="<?= /* @escapeNotVerified */ $block->getUrl('customer/index/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Customers</a> + <a id="searchPreviewCustomers" href="<?= $block->escapeUrl($block->getUrl('customer/index/index/')) ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Customers</a> </li> <li class="item"> - <a id="searchPreviewPages" href="<?= /* @escapeNotVerified */ $block->getUrl('cms/page/index/') ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Pages</a> + <a id="searchPreviewPages" href="<?= $block->escapeUrl($block->getUrl('cms/page/index/')) ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Pages</a> </li> <% if (data.items.length) { %> <% _.each(data.items, function(value){ %> @@ -52,7 +52,7 @@ <% } else { %> <li> <span class="mage-suggest-no-records"> - <?= /* @escapeNotVerified */ __('No records found.') ?> + <?= $block->escapeHtml(__('No records found.')) ?> </span> </li> <% } %> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml index 60cd51b496aa7..fecf5365544e0 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/accordion.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,9 +10,9 @@ */ $items = $block->getItems(); ?> -<?php if (!empty($items)): ?> +<?php if (!empty($items)) : ?> <dl id="tab_content_<?= $block->getHtmlId() ?>" name="tab_content_<?= $block->getHtmlId() ?>" class="accordion"> - <?php foreach ($items as $_item): ?> + <?php foreach ($items as $_item) : ?> <?= $block->getChildHtml($_item->getId()) ?> <?php endforeach ?> </dl> @@ -23,7 +20,7 @@ $items = $block->getItems(); require([ 'mage/adminhtml/accordion' ], function(){ - tab_content_<?= $block->getHtmlId() ?>AccordionJs = new varienAccordion('tab_content_<?= $block->getHtmlId() ?>', '<?= /* @escapeNotVerified */ $block->getShowOnlyOne() ?>'); - }); + tab_content_<?= $block->getHtmlId() ?>AccordionJs = new varienAccordion('tab_content_<?= $block->getHtmlId() ?>', '<?= $block->escapeJs($block->getShowOnlyOne()) ?>'); + }); </script> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/breadcrumbs.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/breadcrumbs.phtml index 74cd4eb7f2c9d..fb7cc63ebc10b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/breadcrumbs.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/breadcrumbs.phtml @@ -3,25 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if (!empty($links)): ?> +<?php if (!empty($links)) : ?> <ul class="breadcrumbs"> <?php $_size = count($links); ?> - <?php foreach ($links as $_index => $_link): ?> + <?php foreach ($links as $_index => $_link) : ?> <li> - <?php if (empty($_link['url'])): ?> - <?php if ($_index != $_size-1): ?> + <?php if (empty($_link['url'])) : ?> + <?php if ($_index != $_size-1) : ?> <span><?= $block->escapeHtml($_link['label']) ?></span> - <?php else: ?> + <?php else : ?> <strong><?= $block->escapeHtml($_link['label']) ?></strong> <?php endif; ?> - <?php else: ?> - <a href="<?= /* @escapeNotVerified */ $_link['url'] ?>" title="<?= $block->escapeHtml($_link['title']) ?>"><?= $block->escapeHtml($_link['label']) ?></a> + <?php else : ?> + <a href="<?= $block->escapeUrl($_link['url']) ?>" title="<?= $block->escapeHtml($_link['title']) ?>"><?= $block->escapeHtml($_link['label']) ?></a> <?php endif; ?> - <?php if ($_index != $_size-1): ?> + <?php if ($_index != $_size-1) : ?> » <?php endif; ?> </li> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/button.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/button.phtml index d3376745afe39..b743b9bee10db 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/button.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/button.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,7 +10,7 @@ */ ?> <?= $block->getBeforeHtml() ?> -<button <?= /* @escapeNotVerified */ $block->getAttributesHtml(), $block->getUiId() ?>> - <span><?= /* @escapeNotVerified */ $block->getLabel() ?></span> +<button <?= /* @noEscape */ $block->getAttributesHtml(), $block->getUiId() ?>> + <span><?= $block->escapeHtml($block->getLabel()) ?></span> </button> <?= $block->getAfterHtml() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml index a115777624e91..0123de098a9e0 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/button/split.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Backend\Block\Widget\Button\SplitButton */ @@ -15,20 +12,20 @@ <button <?= $block->getButtonAttributesHtml() ?>> <span><?= $block->escapeHtml($block->getLabel()) ?></span> </button> - <?php if ($block->hasSplit()): ?> + <?php if ($block->hasSplit()) : ?> <button <?= $block->getToggleAttributesHtml() ?>> <span>Select</span> </button> - <?php if (!$block->getDisabled()): ?> - <ul class="dropdown-menu" <?= /* @escapeNotVerified */ $block->getUiId("dropdown-menu") ?>> - <?php foreach ($block->getOptions() as $key => $option): ?> + <?php if (!$block->getDisabled()) : ?> + <ul class="dropdown-menu" <?= /* @noEscape */ $block->getUiId("dropdown-menu") ?>> + <?php foreach ($block->getOptions() as $key => $option) : ?> <li> <span <?= $block->getOptionAttributesHtml($key, $option) ?>> <?= $block->escapeHtml($option['label']) ?> </span> - <?php if (isset($option['hint'])): ?> - <div class="tooltip" <?= /* @escapeNotVerified */ $block->getUiId('item', $key, 'tooltip') ?>> + <?php if (isset($option['hint'])) : ?> + <div class="tooltip" <?= /* @noEscape */ $block->getUiId('item', $key, 'tooltip') ?>> <a href="<?= $block->escapeHtml($option['hint']['href']) ?>" class="help"> <?= $block->escapeHtml($option['hint']['label']) ?> </a> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form.phtml index dc7d02795a054..62f8d25ce1be2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\Widget\Form */ ?> <?php /* @todo replace .form-inline with better class name */?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/container.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/container.phtml index bcbda8fc761ac..aa289dbf1eb0f 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/container.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/container.phtml @@ -4,15 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\Widget\Form\Container */ - ?> -<?= /* @escapeNotVerified */ $block->getFormInitScripts() ?> -<?php if ($block->getButtonsHtml('header')): ?> - <div class="page-form-actions" <?= /* @escapeNotVerified */ $block->getUiId('content-header') ?>><?= $block->getButtonsHtml('header') ?></div> +?> +<?= /* @noEscape */ $block->getFormInitScripts() ?> +<?php if ($block->getButtonsHtml('header')) : ?> + <div class="page-form-actions" <?= /* @noEscape */ $block->getUiId('content-header') ?>><?= $block->getButtonsHtml('header') ?></div> <?php endif; ?> -<?php if ($block->getButtonsHtml('toolbar')): ?> +<?php if ($block->getButtonsHtml('toolbar')) : ?> <div class="page-main-actions"> <div class="page-actions"> <div class="page-actions-buttons"> @@ -22,7 +20,7 @@ </div> <?php endif; ?> <?= $block->getFormHtml() ?> -<?php if ($block->hasFooterButtons()): ?> +<?php if ($block->hasFooterButtons()) : ?> <div class="content-footer"> <p class="form-buttons"><?= $block->getButtonsHtml('footer') ?></p> </div> @@ -36,7 +34,7 @@ require([ $('#edit_form').form() .validation({ - validationUrl: '<?= /* @escapeNotVerified */ $block->getValidationUrl() ?>', + validationUrl: '<?= $block->escapeJs($block->getValidationUrl()) ?>', highlight: function(element) { var detailsElement = $(element).closest('details'); if (detailsElement.length && detailsElement.is('.details')) { @@ -51,4 +49,4 @@ require([ }); </script> -<?= /* @escapeNotVerified */ $block->getFormScripts() ?> +<?= /* @noEscape */ $block->getFormScripts() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml index 720bc1e58259d..ec53f7e5c74ce 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element.phtml @@ -4,59 +4,46 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +$type = $element->getType(); ?> -<?php switch ($element->getType()) { - case 'fieldset': ?> - +<?php if ($type === 'fieldset') : ?> <fieldset> - <legend><?= /* @escapeNotVerified */ $element->getLegend() ?></legend><br /> - <?php foreach ($element->getElements() as $_element): ?> - <?= /* @escapeNotVerified */ $formBlock->drawElement($_element) ?> + <legend><?= $block->escapeHtml($element->getLegend()) ?></legend><br /> + <?php foreach ($element->getElements() as $_element) : ?> + <?= /* @noEscape */ $formBlock->drawElement($_element) ?> <?php endforeach; ?> </fieldset> - <?php break; - case 'column': ?> - <?php break; - case 'hidden': ?> - <input type="<?= /* @escapeNotVerified */ $element->getType() ?>" name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $element->getValue() ?>"> - <?php break; - case 'select': ?> +<?php elseif ($type === 'hidden') : ?> + <input type="<?= $block->escapeHtmlAttr($element->getType()) ?>" name="<?= $block->escapeHtmlAttr($element->getName()) ?>" id="<?= $element->getHtmlId() ?>" value="<?= $block->escapeHtmlAttr($element->getValue()) ?>"> + <?php elseif ($type === 'select') : ?> <span class="form_row"> - <?php if ($element->getLabel()): ?><label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label><?php endif; ?> - <select name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" class="select<?= /* @escapeNotVerified */ $element->getClass() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>"> - <?php foreach ($element->getValues() as $_value): ?> - <option <?= /* @escapeNotVerified */ $_value->serialize() ?><?php if ($_value->getValue() == $element->getValue()): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ $_value->getLabel() ?></option> + <?php if ($element->getLabel()) : ?><label for="<?= $element->getHtmlId() ?>"><?= $block->escapeHtml($element->getLabel()) ?>:</label><?php endif; ?> + <select name="<?= $block->escapeHtmlAttr($element->getName()) ?>" id="<?= $element->getHtmlId() ?>" class="select<?= $block->escapeHtmlAttr($element->getClass()) ?>" title="<?= $block->escapeHtmlAttr($element->getTitle()) ?>"> + <?php foreach ($element->getValues() as $_value) : ?> + <option <?= /* @noEscape */ $_value->serialize() ?><?php if ($_value->getValue() == $element->getValue()) : ?> selected="selected"<?php endif; ?>><?= $block->escapeHtml($_value->getLabel()) ?></option> <?php endforeach; ?> </select> </span> - <?php break; - case 'text': - case 'button': - case 'password': ?> +<?php elseif ($type === 'text' || $type === 'button' || $type === 'password') : ?> <span class="form_row"> - <?php if ($element->getLabel()): ?><label for="<?= $element->getHtmlId() ?>" <?= /* @escapeNotVerified */ $block->getUiId('label') ?>><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label><?php endif; ?> - <input type="<?= /* @escapeNotVerified */ $element->getType() ?>" name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $element->getValue() ?>" class="input-text <?= /* @escapeNotVerified */ $element->getClass() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>" <?= ($element->getOnClick() ? 'onClick="' . $element->getOnClick() . '"' : '') ?>/> + <?php if ($element->getLabel()) : ?><label for="<?= $element->getHtmlId() ?>" <?= /* @noEscape */ $block->getUiId('label') ?>><?= $block->escapeHtml($element->getLabel()) ?>:</label><?php endif; ?> + <input type="<?= $block->escapeHtmlAttr($element->getType()) ?>" name="<?= $block->escapeHtmlAttr($element->getName()) ?>" id="<?= /* @noEscape */ $element->getHtmlId() ?>" value="<?= $block->escapeHtmlAttr($element->getValue()) ?>" class="input-text <?= $block->escapeHtmlAttr($element->getClass()) ?>" title="<?= $block->escapeHtmlAttr($element->getTitle()) ?>" <?= /* @noEscape */ ($element->getOnClick() ? 'onClick="' . $element->getOnClick() . '"' : '') ?>/> </span> - <?php break; - case 'radio': ?> +<?php elseif ($type === 'radio') : ?> <span class="form_row"> - <?php if ($element->getLabel()): ?><label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label><?php endif; ?> - <input type="<?= /* @escapeNotVerified */ $element->getType() ?>" name="<?= /* @escapeNotVerified */ $element->getName() ?>" id="<?= $element->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $element->getValue() ?>" class="input-text <?= /* @escapeNotVerified */ $element->getClass() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>"/> + <?php if ($element->getLabel()) : ?><label for="<?= $element->getHtmlId() ?>"><?= $block->escapeHtml($element->getLabel()) ?>:</label><?php endif; ?> + <input type="<?= $block->escapeHtmlAttr($element->getType()) ?>" name="<?= $block->escapeHtmlAttr($element->getName()) ?>" id="<?= $element->getHtmlId() ?>" value="<?= $block->escapeHtmlAttr($element->getValue()) ?>" class="input-text <?= $block->escapeHtmlAttr($element->getClass()) ?>" title="<?= $block->escapeHtmlAttr($element->getTitle()) ?>"/> </span> - <?php break; - case 'radios': ?> +<?php elseif ($type === 'radios') : ?> <span class="form_row"> - <label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label> - <?php foreach ($element->getRadios() as $_radio): ?> - <input type="radio" name="<?= /* @escapeNotVerified */ $_radio->getName() ?>" id="<?= $_radio->getHtmlId() ?>" value="<?= /* @escapeNotVerified */ $_radio->getValue() ?>" class="input-radio <?= /* @escapeNotVerified */ $_radio->getClass() ?>" title="<?= /* @escapeNotVerified */ $_radio->getTitle() ?>" <?= ($_radio->getValue() == $element->getChecked()) ? 'checked="true"' : '' ?> > <?= /* @escapeNotVerified */ $_radio->getLabel() ?> + <label for="<?= $element->getHtmlId() ?>"><?= $block->escapeHtml($element->getLabel()) ?>:</label> + <?php foreach ($element->getRadios() as $_radio) : ?> + <input type="radio" name="<?= $block->escapeHtmlAttr($_radio->getName()) ?>" id="<?= $_radio->getHtmlId() ?>" value="<?= $block->escapeHtmlAttr($_radio->getValue()) ?>" class="input-radio <?= $block->escapeHtmlAttr($_radio->getClass()) ?>" title="<?= $block->escapeHtmlAttr($_radio->getTitle()) ?>" <?= ($_radio->getValue() == $element->getChecked()) ? 'checked="true"' : '' ?> > <?= $block->escapeHtml($_radio->getLabel()) ?> <?php endforeach; ?> </span> - <?php break; - case 'wysiwyg': ?> +<?php elseif ($type === 'wysiwyg') : ?> <span class="form_row"> - <label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label> + <label for="<?= $element->getHtmlId() ?>"><?= $block->escapeHtml($element->getLabel()) ?>:</label> <script> require([ "wysiwygAdapter" @@ -65,7 +52,7 @@ tinyMCE.init({ mode : "exact", theme : "advanced", - elements : "<?= /* @escapeNotVerified */ $element->getName() ?>", + elements : "<?= $block->escapeJs($element->getName()) ?>", plugins : "inlinepopups,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,zoom,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras", theme_advanced_buttons1 : "newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect", theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor", @@ -84,24 +71,16 @@ }); }); </script> - <textarea name="<?= /* @escapeNotVerified */ $element->getName() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>" id="<?= $element->getHtmlId() ?>" class="textarea <?= /* @escapeNotVerified */ $element->getClass() ?>" cols="80" rows="20"><?= /* @escapeNotVerified */ $element->getValue() ?></textarea> + <textarea name="<?= $block->escapeHtmlAttr($element->getName()) ?>" title="<?= $block->escapeHtmlAttr($element->getTitle()) ?>" id="<?= $element->getHtmlId() ?>" class="textarea <?= $block->escapeHtmlAttr($element->getClass()) ?>" cols="80" rows="20"><?= $block->escapeHtml($element->getValue()) ?></textarea> </span> - <?php break; - case 'textarea': ?> +<?php elseif ($type === 'textarea') : ?> <span class="form_row"> - <label for="<?= $element->getHtmlId() ?>"><?= /* @escapeNotVerified */ $element->getLabel() ?>:</label> - <textarea name="<?= /* @escapeNotVerified */ $element->getName() ?>" title="<?= /* @escapeNotVerified */ $element->getTitle() ?>" id="<?= $element->getHtmlId() ?>" class="textarea <?= /* @escapeNotVerified */ $element->getClass() ?>" cols="15" rows="2"><?= /* @escapeNotVerified */ $element->getValue() ?></textarea> + <label for="<?= $element->getHtmlId() ?>"><?= $block->escapeHtml($element->getLabel()) ?>:</label> + <textarea name="<?= $block->escapeHtmlAttr($element->getName()) ?>" title="<?= $block->escapeHtmlAttr($element->getTitle()) ?>" id="<?= $element->getHtmlId() ?>" class="textarea <?= $block->escapeHtmlAttr($element->getClass()) ?>" cols="15" rows="2"><?= $block->escapeHtml($element->getValue()) ?></textarea> </span> - <?php break; - case 'editor': ?> - <?php break; - case 'file': ?> - <?php break; - case 'checkbox': ?> - <?php break; -} ?> -<?php if ($element->getScript()): ?> +<?php endif; ?> +<?php if ($element->getScript()) : ?> <script> - <?= /* @escapeNotVerified */ $element->getScript() ?> + <?= /* @noEscape */ $element->getScript() ?> </script> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml index e11c0efc123ff..5c07b35e72a19 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml @@ -3,20 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <tr> <td colspan="2"> -<label for="gallery"><?= /* @escapeNotVerified */ __('Images') ?></label> +<label for="gallery"><?= $block->escapeHtml(__('Images')) ?></label> <table id="gallery" class="gallery" border="0" cellspacing="3" cellpadding="0"> <thead id="gallery_thead" class="gallery"> <tr class="gallery"> - <td class="gallery" valign="middle" align="center"><?= /* @escapeNotVerified */ __('Big Image') ?></td> - <td class="gallery" valign="middle" align="center"><?= /* @escapeNotVerified */ __('Thumbnail') ?></td> - <td class="gallery" valign="middle" align="center"><?= /* @escapeNotVerified */ __('Sort Order') ?></td> - <td class="gallery" valign="middle" align="center"><?= /* @escapeNotVerified */ __('Delete') ?></td> + <td class="gallery" valign="middle" align="center"><?= $block->escapeHtml(__('Big Image')) ?></td> + <td class="gallery" valign="middle" align="center"><?= $block->escapeHtml(__('Thumbnail')) ?></td> + <td class="gallery" valign="middle" align="center"><?= $block->escapeHtml(__('Sort Order')) ?></td> + <td class="gallery" valign="middle" align="center"><?= $block->escapeHtml(__('Delete')) ?></td> </tr> </thead> @@ -28,22 +25,22 @@ <tbody class="gallery"> -<?php $i = 0; if (!is_null($block->getValues())): ?> - <?php foreach ($block->getValues() as $image): $i++; ?> - <tr id="<?= $block->getElement()->getHtmlId() ?>_tr_<?= /* @escapeNotVerified */ $image->getValueId() ?>" class="gallery"> - <?php foreach ($block->getValues()->getAttributeBackend()->getImageTypes() as $type): ?> +<?php $i = 0; if ($block->getValues() !== null) : ?> + <?php foreach ($block->getValues() as $image) : $i++; ?> + <tr id="<?= $block->getElement()->getHtmlId() ?>_tr_<?= $block->escapeHtmlAttr($image->getValueId()) ?>" class="gallery"> + <?php foreach ($block->getValues()->getAttributeBackend()->getImageTypes() as $type) : ?> <td class="gallery" align="center" style="vertical-align:bottom;"> - <a href="<?= /* @escapeNotVerified */ $image->setType($type)->getSourceUrl() ?>" target="_blank" onclick="imagePreview('<?= $block->getElement()->getHtmlId() ?>_image_<?= /* @escapeNotVerified */ $type ?>_<?= /* @escapeNotVerified */ $image->getValueId() ?>');return false;"> - <img id="<?= $block->getElement()->getHtmlId() ?>_image_<?= /* @escapeNotVerified */ $type ?>_<?= /* @escapeNotVerified */ $image->getValueId() ?>" src="<?= /* @escapeNotVerified */ $image->setType($type)->getSourceUrl() ?>?<?= /* @escapeNotVerified */ time() ?>" alt="<?= /* @escapeNotVerified */ $image->getValue() ?>" title="<?= /* @escapeNotVerified */ $image->getValue() ?>" height="25" class="small-image-preview v-middle"/></a><br/> - <input type="file" name="<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>_<?= /* @escapeNotVerified */ $type ?>[<?= /* @escapeNotVerified */ $image->getValueId() ?>]" size="1"></td> + <a href="<?= $block->escapeUrl($image->setType($type)->getSourceUrl()) ?>" target="_blank" onclick="imagePreview('<?= $block->getElement()->getHtmlId() ?>_image_<?= $block->escapeHtmlAttr($block->escapeJs($type)) ?>_<?= $block->escapeHtmlAttr($block->escapeJs($image->getValueId())) ?>');return false;"> + <img id="<?= $block->getElement()->getHtmlId() ?>_image_<?= $block->escapeHtmlAttr($type) ?>_<?= $block->escapeHtmlAttr($image->getValueId()) ?>" src="<?= $block->escapeUrl($image->setType($type)->getSourceUrl()) ?>?<?= /* @noEscape */ time() ?>" alt="<?= $block->escapeHtmlAttr($image->getValue()) ?>" title="<?= $block->escapeHtmlAttr($image->getValue()) ?>" height="25" class="small-image-preview v-middle"/></a><br/> + <input type="file" name="<?= $block->escapeHtmlAttr($block->getElement()->getName()) ?>_<?= $block->escapeHtmlAttr($type) ?>[<?= $block->escapeHtmlAttr($image->getValueId()) ?>]" size="1"></td> <?php endforeach; ?> - <td class="gallery" align="center" style="vertical-align:bottom;"><input type="input" name="<?= /* @escapeNotVerified */ $block->getElement()->getParentName() ?>[position][<?= /* @escapeNotVerified */ $image->getValueId() ?>]" value="<?= /* @escapeNotVerified */ $image->getPosition() ?>" id="<?= $block->getElement()->getHtmlId() ?>_position_<?= /* @escapeNotVerified */ $image->getValueId() ?>" size="3"/></td> - <td class="gallery" align="center" style="vertical-align:bottom;"><?= $block->getDeleteButtonHtml($image->getValueId()) ?><input type="hidden" name="<?= /* @escapeNotVerified */ $block->getElement()->getParentName() ?>[delete][<?= /* @escapeNotVerified */ $image->getValueId() ?>]" id="<?= $block->getElement()->getHtmlId() ?>_delete_<?= /* @escapeNotVerified */ $image->getValueId() ?>"/></td> + <td class="gallery" align="center" style="vertical-align:bottom;"><input type="input" name="<?= $block->escapeHtmlAttr($block->getElement()->getParentName()) ?>[position][<?= $block->escapeHtmlAttr($image->getValueId()) ?>]" value="<?= $block->escapeHtmlAttr($image->getPosition()) ?>" id="<?= $block->getElement()->getHtmlId() ?>_position_<?= $block->escapeHtmlAttr($image->getValueId()) ?>" size="3"/></td> + <td class="gallery" align="center" style="vertical-align:bottom;"><?= $block->getDeleteButtonHtml($image->getValueId()) ?><input type="hidden" name="<?= $block->escapeHtmlAttr($block->getElement()->getParentName()) ?>[delete][<?= $block->escapeHtmlAttr($image->getValueId()) ?>]" id="<?= $block->getElement()->getHtmlId() ?>_delete_<?= $block->escapeHtmlAttr($image->getValueId()) ?>"/></td> </tr> <?php endforeach; ?> <?php endif; ?> -<?php if ($i == 0): ?> +<?php if ($i == 0) : ?> <script> document.getElementById("gallery_thead").style.visibility="hidden"; </script> @@ -56,7 +53,7 @@ require([ 'prototype' ], function () { id = 0; -num_of_images = <?= /* @escapeNotVerified */ $i ?>; +num_of_images = <?= /* @noEscape */ $i ?>; window.addNewImage = function() { @@ -65,17 +62,19 @@ window.addNewImage = function() id--; num_of_images++; - new_file_input = '<input type="file" name="<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>_%j%[%id%]" size="1">'; + new_file_input = '<input type="file" name="<?= $block->escapeHtmlAttr($block->getElement()->getName()) ?>_%j%[%id%]" size="1">'; // Sort order input var new_row_input = document.createElement( 'input' ); new_row_input.type = 'text'; - new_row_input.name = '<?= /* @escapeNotVerified */ $block->getElement()->getParentName() ?>[position]['+id+']'; + new_row_input.name = '<?= $block->escapeJs($block->getElement()->getParentName()) ?>[position]['+id+']'; new_row_input.size = '3'; new_row_input.value = '0'; // Delete button - new_row_button = <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getDeleteButtonHtml("this")) ?>; + <?php //phpcs:disable ?> + new_row_button = <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getDeleteButtonHtml("this")) ?>; + <?php // phpcs:enable ?> table = document.getElementById( "gallery" ); @@ -114,8 +113,8 @@ window.deleteImage = function(image) document.getElementById("gallery_thead").style.visibility="hidden"; } if (image>0) { - document.getElementById('<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>_delete_'+image).value=image; - document.getElementById('<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>_tr_'+image).style.display='none'; + document.getElementById('<?= $block->escapeJs($block->getElement()->getName()) ?>_delete_'+image).value=image; + document.getElementById('<?= $block->escapeJs($block->getElement()->getName()) ?>_tr_'+image).style.display='none'; } else { image.parentNode.parentNode.parentNode.removeChild( image.parentNode.parentNode ); } diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/element.phtml index ae0bcb826a1a3..e74e5b1e0fe94 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/element.phtml @@ -3,16 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_element = $block->getElement() ?> -<?php if ($_element->getNoSpan() !== true): ?> +<?php if ($_element->getNoSpan() !== true) : ?> <span class="field-row"> <?php endif; ?> <?= $_element->getLabelHtml() ?> <?= $_element->getElementHtml() ?> -<?php if ($_element->getNoSpan() !== true): ?> +<?php if ($_element->getNoSpan() !== true) : ?> </span> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml index aaf1cb5ff550e..7eb6e95fa85bf 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $element \Magento\Framework\Data\Form\Element\Fieldset */ @@ -32,40 +29,43 @@ if ($isField) { <?php /** -* @todo investigate situations, when the following is needed: -* echo $element->getHeaderBar(); -* echo $element->getSubFieldsetHtml(); -*/ ?> + * @todo investigate situations, when the following is needed: + * echo $element->getHeaderBar(); + * echo $element->getSubFieldsetHtml(); + */ ?> -<?php if ($isWrapped): ?> +<?php if ($isWrapped) : ?> <div class="fieldset-wrapper <?= ($isCollapsable) ? 'admin__collapsible-block-wrapper ' : '' ?>" - id="<?= /* @escapeNotVerified */ $containerId ? $containerId : $id . '-wrapper' ?>" - data-role="<?= /* @escapeNotVerified */ $id ?>-wrapper"> + id="<?= $block->escapeHtmlAttr($containerId ? $containerId : $id . '-wrapper') ?>" + data-role="<?= $block->escapeHtmlAttr($id) ?>-wrapper"> <div class="fieldset-wrapper-title admin__fieldset-wrapper-title"> - <strong <?php /* @escapeNotVerified */ echo($isCollapsable) ? + <strong <?= /* @noEscape */ $isCollapsable ? 'class="admin__collapsible-title" data-toggle="collapse" data-target="#' . $id . '-content"' : 'class="title"'; ?>> - <span><?= /* @escapeNotVerified */ $element->getLegend() ?></span> + <span><?= $block->escapeHtml($element->getLegend()) ?></span> </strong> - <?= /* @escapeNotVerified */ $titleActions ?> + <?= /* @noEscape */ $titleActions ?> </div> <div class="fieldset-wrapper-content admin__fieldset-wrapper-content<?= ($isCollapsable) ? ' collapse' : '' ?>" - id="<?= /* @escapeNotVerified */ $id ?>-content" - data-role="<?= /* @escapeNotVerified */ $id ?>-content"> + id="<?= $block->escapeHtmlAttr($id) ?>-content" + data-role="<?= $block->escapeHtmlAttr($id) ?>-content"> <?php endif; ?> - <?php if (!$element->getNoContainer()): ?> - <fieldset class="<?= /* @escapeNotVerified */ $cssClass ?>" id="<?= /* @escapeNotVerified */ $id ?>"> - <?php if ($element->getLegend() && !$isWrapped): ?> - <legend class="<?= /* @escapeNotVerified */ $isField ? 'label admin__field-label' : 'admin__legend legend' ?>"> - <span><?= /* @escapeNotVerified */ $element->getLegend() ?></span> + <?php if (!$element->getNoContainer()) : ?> + <fieldset class="<?= $block->escapeHtmlAttr($cssClass) ?>" id="<?= $block->escapeHtmlAttr($id) ?>"> + <?php if (strlen($element->getBeforeElementHtml())) : ?> + <?= $element->getBeforeElementHtml() ?> + <?php endif ?> + <?php if ($element->getLegend() && !$isWrapped) : ?> + <legend class="<?= /* @noEscape */ $isField ? 'label admin__field-label' : 'admin__legend legend' ?>"> + <span><?= $block->escapeHtml($element->getLegend()) ?></span> </legend><br /> <?php endif; ?> <?php endif; ?> <div class="messages"> - <?php if ($element->getComment() && !$isField): ?> + <?php if ($element->getComment() && !$isField) : ?> <div class="message message-notice"><?= $block->escapeHtml($element->getComment()) ?></div> <?php endif; ?> </div> @@ -73,34 +73,34 @@ if ($isField) { <?= ($isField) ? '<div class="control admin__field-control">' : '' ?> - <?php if ($element->hasHtmlContent() && !$isField): ?> + <?php if ($element->hasHtmlContent() && !$isField) : ?> <?= $element->getHtmlContent() ?> - <?php else: ?> + <?php else : ?> - <?php if ($isField && $count > 1):?> - <div class="fields-group-<?= /* @escapeNotVerified */ $count ?>"> + <?php if ($isField && $count > 1) : ?> + <div class="fields-group-<?= /* @noEscape */ $count ?>"> <?php endif; ?> <?= $element->getBasicChildrenHtml() ?> <?= ($isField && $count > 1) ? '</div>' : '' ?> - <?php if ($element->getComment() && $isField): ?> + <?php if ($element->getComment() && $isField) : ?> <div class="note"><?= $block->escapeHtml($element->getComment()) ?></div> <?php endif; ?> - <?php if ($element->hasAdvanced() && !$isField): ?> + <?php if ($element->hasAdvanced() && !$isField) : ?> <?= (!$element->getNoContainer() && $advancedAfter) ? '</fieldset>' : '' ?> - <details data-mage-init='{"details": {}}' class="details admin__collapsible-block-wrapper" id="details<?= /* @escapeNotVerified */ $id ?>"> - <summary class="details-summary admin__collapsible-title" id="details-summary<?= /* @escapeNotVerified */ $id ?>"> - <span><?= /* @escapeNotVerified */ $advancedLabel ?></span> + <details data-mage-init='{"details": {}}' class="details admin__collapsible-block-wrapper" id="details<?= /* @noEscape */ $id ?>"> + <summary class="details-summary admin__collapsible-title" id="details-summary<?= /* @noEscape */ $id ?>"> + <span><?= $block->escapeHtml($advancedLabel) ?></span> </summary> - <div class="details-content admin__fieldset" id="details-content<?= /* @escapeNotVerified */ $id ?>"> + <div class="details-content admin__fieldset" id="details-content<?= /* @noEscape */ $id ?>"> <?= $element->getAdvancedChildrenHtml() ?> </div> </details> - <?php elseif ($element->hasAdvanced() && $isField): ?> - <div class="nested" id="nested<?= /* @escapeNotVerified */ $id ?>"> + <?php elseif ($element->hasAdvanced() && $isField) : ?> + <div class="nested" id="nested<?= /* @noEscape */ $id ?>"> <?= $element->getAdvancedChildrenHtml() ?> </div> <?php endif; ?> @@ -110,11 +110,11 @@ if ($isField) { <?php endif; ?> - <?php if (!$element->getNoContainer() && !$advancedAfter): ?> + <?php if (!$element->getNoContainer() && !$advancedAfter) : ?> </fieldset> <?php endif; ?> -<?php if ($isWrapped): ?> +<?php if ($isWrapped) : ?> </div> </div> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml index 3608ed7662e49..bec6fe84fb2b1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element */ @@ -26,20 +23,20 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' . ($element->getFieldExtraAttributes() ? ' ' . $element->getFieldExtraAttributes() : ''); ?> -<?php if (!$element->getNoDisplay()): ?> - <?php if ($element->getType() == 'hidden'): ?> +<?php if (!$element->getNoDisplay()) : ?> + <?php if ($element->getType() == 'hidden') : ?> <?= $element->getElementHtml() ?> - <?php else: ?> - <div<?= /* @escapeNotVerified */ $fieldAttributes ?>> - <?php if ($elementBeforeLabel): ?> + <?php else : ?> + <div<?= /* @noEscape */ $fieldAttributes ?>> + <?php if ($elementBeforeLabel) : ?> <?= $element->getElementHtml() ?> <?= $element->getLabelHtml('', $element->getScopeLabel()) ?> - <?= /* @escapeNotVerified */ $note ?> - <?php else: ?> + <?= /* @noEscape */ $note ?> + <?php else : ?> <?= $element->getLabelHtml('', $element->getScopeLabel()) ?> <div class="admin__field-control control"> - <?= /* @escapeNotVerified */ ($addOn) ? '<div class="admin__field">' . $element->getElementHtml() . '</div>' : $element->getElementHtml() ?> - <?= /* @escapeNotVerified */ $note ?> + <?= /* @noEscape */ ($addOn) ? '<div class="admin__field">' . $element->getElementHtml() . '</div>' : $element->getElementHtml() ?> + <?= /* @noEscape */ $note ?> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index fad8f5968009f..63cdae13490ac 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -19,101 +16,101 @@ * */ /* @var $block \Magento\Backend\Block\Widget\Grid */ -$numColumns = !is_null($block->getColumns()) ? sizeof($block->getColumns()) : 0; +$numColumns = $block->getColumns() !== null ? count($block->getColumns()) : 0; ?> -<?php if ($block->getCollection()): ?> +<?php if ($block->getCollection()) : ?> -<?php if ($block->canDisplayContainer()): ?> + <?php if ($block->canDisplayContainer()) : ?> <div id="<?= $block->escapeHtml($block->getId()) ?>" data-grid-id="<?= $block->escapeHtml($block->getId()) ?>"> -<?php else: ?> -<?= $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> -<?php endif; ?> + <?php else : ?> + <?= $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> + <?php endif; ?> <div class="admin__data-grid-header admin__data-grid-toolbar"> <?php $massActionAvailable = $block->getChildBlock('grid.massaction') && $block->getChildBlock('grid.massaction')->isAvailable() ?> - <?php if ($block->getPagerVisibility() || $block->getExportTypes() || $block->getChildBlock('grid.columnSet')->getFilterVisibility() || $massActionAvailable): ?> + <?php if ($block->getPagerVisibility() || $block->getExportTypes() || $block->getChildBlock('grid.columnSet')->getFilterVisibility() || $massActionAvailable) : ?> <div class="admin__data-grid-header-row"> - <?php if ($massActionAvailable): ?> + <?php if ($massActionAvailable) : ?> <?= $block->getMainButtonsHtml() ? '<div class="admin__filter-actions">' . $block->getMainButtonsHtml() . '</div>' : '' ?> <?php endif; ?> - <?php if ($block->getChildBlock('grid.export')): ?> + <?php if ($block->getChildBlock('grid.export')) : ?> <?= $block->getChildHtml('grid.export') ?> <?php endif; ?> </div> <?php endif; ?> - <div class="<?php if($massActionAvailable) { echo '_massaction ';} ?>admin__data-grid-header-row"> - <?php if ($massActionAvailable): ?> + <div class="<?php if ($massActionAvailable) { echo '_massaction ';} ?>admin__data-grid-header-row"> + <?php if ($massActionAvailable) : ?> <?= $block->getChildHtml('grid.massaction') ?> - <?php else: ?> + <?php else : ?> <?= $block->getMainButtonsHtml() ? '<div class="admin__filter-actions">' . $block->getMainButtonsHtml() . '</div>' : '' ?> <?php endif; ?> <?php $countRecords = $block->getCollection()->getSize(); ?> <div class="admin__control-support-text"> - <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?= /* @escapeNotVerified */ $block->getUiId('total-count') ?>> - <?= /* @escapeNotVerified */ $countRecords ?> + <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?= /* @noEscape */ $block->getUiId('total-count') ?>> + <?= /* @noEscape */ $countRecords ?> </span> - <?= /* @escapeNotVerified */ __('records found') ?> + <?= $block->escapeHtml(__('records found')) ?> <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>_massaction-count" - class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?= /* @escapeNotVerified */ __('selected') ?></span></span> + class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?= $block->escapeHtml(__('selected')) ?></span></span> </div> - <?php if ($block->getPagerVisibility()): ?> + <?php if ($block->getPagerVisibility()) : ?> <div class="admin__data-grid-pager-wrap"> - <select name="<?= /* @escapeNotVerified */ $block->getVarNameLimit() ?>" + <select name="<?= $block->escapeHtmlAttr($block->getVarNameLimit()) ?>" id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" - onchange="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.loadByElement(this)" <?= /* @escapeNotVerified */ $block->getUiId('per-page') ?> + onchange="<?= /* @noEscape */ $block->getJsObjectName() ?>.loadByElement(this)" <?= /* @noEscape */ $block->getUiId('per-page') ?> class="admin__control-select"> - <option value="20"<?php if ($block->getCollection()->getPageSize() == 20): ?> + <option value="20"<?php if ($block->getCollection()->getPageSize() == 20) : ?> selected="selected"<?php endif; ?>>20 </option> - <option value="30"<?php if ($block->getCollection()->getPageSize() == 30): ?> + <option value="30"<?php if ($block->getCollection()->getPageSize() == 30) : ?> selected="selected"<?php endif; ?>>30 </option> - <option value="50"<?php if ($block->getCollection()->getPageSize() == 50): ?> + <option value="50"<?php if ($block->getCollection()->getPageSize() == 50) : ?> selected="selected"<?php endif; ?>>50 </option> - <option value="100"<?php if ($block->getCollection()->getPageSize() == 100): ?> + <option value="100"<?php if ($block->getCollection()->getPageSize() == 100) : ?> selected="selected"<?php endif; ?>>100 </option> - <option value="200"<?php if ($block->getCollection()->getPageSize() == 200): ?> + <option value="200"<?php if ($block->getCollection()->getPageSize() == 200) : ?> selected="selected"<?php endif; ?>>200 </option> </select> <label for="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" - class="admin__control-support-text"><?= /* @escapeNotVerified */ __('per page') ?></label> + class="admin__control-support-text"><?= $block->escapeHtml(__('per page')) ?></label> <div class="admin__data-grid-pager"> <?php $_curPage = $block->getCollection()->getCurPage() ?> <?php $_lastPage = $block->getCollection()->getLastPageNumber() ?> - <?php if ($_curPage > 1): ?> + <?php if ($_curPage > 1) : ?> <button class="action-previous" type="button" - onclick="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setPage('<?= /* @escapeNotVerified */ ($_curPage - 1) ?>');return false;"> - <span><?= /* @escapeNotVerified */ __('Previous page') ?></span> + onclick="<?= /* @noEscape */ $block->getJsObjectName() ?>.setPage('<?= /* @noEscape */ ($_curPage - 1) ?>');return false;"> + <span><?= $block->escapeHtml(__('Previous page')) ?></span> </button> - <?php else: ?> - <button type="button" class="action-previous disabled"><span><?= /* @escapeNotVerified */ __('Previous page') ?></span></button> + <?php else : ?> + <button type="button" class="action-previous disabled"><span><?= $block->escapeHtml(__('Previous page')) ?></span></button> <?php endif; ?> <input type="text" id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current" - name="<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>" - value="<?= /* @escapeNotVerified */ $_curPage ?>" + name="<?= $block->escapeHtmlAttr($block->getVarNamePage()) ?>" + value="<?= $block->escapeHtmlAttr($_curPage) ?>" class="admin__control-text" - onkeypress="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.inputPage(event, '<?= /* @escapeNotVerified */ $_lastPage ?>')" <?= /* @escapeNotVerified */ $block->getUiId('current-page') ?> /> + onkeypress="<?= /* @noEscape */ $block->getJsObjectName() ?>.inputPage(event, '<?= /* @noEscape */ $_lastPage ?>')" <?= /* @noEscape */ $block->getUiId('current-page') ?> /> <label class="admin__control-support-text" for="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current"> - <?= /* @escapeNotVerified */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> + <?= /* @noEscape */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> - <?php if ($_curPage < $_lastPage): ?> - <button type="button" title="<?= /* @escapeNotVerified */ __('Next page') ?>" + <?php if ($_curPage < $_lastPage) : ?> + <button type="button" title="<?= $block->escapeHtmlAttr(__('Next page')) ?>" class="action-next" - onclick="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setPage('<?= /* @escapeNotVerified */ ($_curPage + 1) ?>');return false;"> - <span><?= /* @escapeNotVerified */ __('Next page') ?></span> + onclick="<?= /* @noEscape */ $block->getJsObjectName() ?>.setPage('<?= /* @noEscape */ ($_curPage + 1) ?>');return false;"> + <span><?= $block->escapeHtml(__('Next page')) ?></span> </button> - <?php else: ?> - <button type="button" class="action-next disabled"><span><?= /* @escapeNotVerified */ __('Next page') ?></span></button> + <?php else : ?> + <button type="button" class="action-next disabled"><span><?= $block->escapeHtml(__('Next page')) ?></span></button> <?php endif; ?> </div> </div> @@ -121,77 +118,77 @@ $numColumns = !is_null($block->getColumns()) ? sizeof($block->getColumns()) : 0; </div> </div> <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> - <?php if ($block->getGridCssClass()): ?> - <table class="<?= /* @escapeNotVerified */ $block->getGridCssClass() ?> data-grid" id="<?= $block->escapeHtml($block->getId()) ?>_table"> + <?php if ($block->getGridCssClass()) : ?> + <table class="<?= $block->escapeHtmlAttr($block->getGridCssClass()) ?> data-grid" id="<?= $block->escapeHtml($block->getId()) ?>_table"> <!-- Rendering column set --> <?= $block->getChildHtml('grid.columnSet') ?> </table> - <?php else: ?> + <?php else : ?> <table class="data-grid" id="<?= $block->escapeHtml($block->getId()) ?>_table"> <!-- Rendering column set --> <?= $block->getChildHtml('grid.columnSet') ?> </table> - <?php if ($block->getChildBlock('grid.bottom.links')): ?> + <?php if ($block->getChildBlock('grid.bottom.links')) : ?> <?= $block->getChildHtml('grid.bottom.links') ?> <?php endif; ?> <?php endif ?> </div> -<?php if ($block->canDisplayContainer()): ?> + <?php if ($block->canDisplayContainer()) : ?> </div> <script> var deps = []; - <?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> deps.push('uiRegistry'); - <?php endif; ?> + <?php endif; ?> - <?php if (strpos($block->getRowClickCallback(), 'order.') !== false): ?> + <?php if (strpos($block->getRowClickCallback(), 'order.') !== false) : ?> deps.push('Magento_Sales/order/create/form'); deps.push('jquery'); - <?php endif; ?> + <?php endif; ?> deps.push('mage/adminhtml/grid'); require(deps, function(<?= ($block->getDependencyJsObject() ? 'registry' : '') ?>){ <?php //TODO: getJsObjectName and getRowClickCallback has unexpected behavior. Should be removed ?> - <?php if ($block->getDependencyJsObject()): ?> - registry.get('<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>', function (<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>) { + <?php if ($block->getDependencyJsObject()) : ?> + registry.get('<?= $block->escapeJs($block->getDependencyJsObject()) ?>', function (<?= $block->escapeJs($block->getDependencyJsObject()) ?>) { <?php endif; ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?> = new varienGrid('<?= $block->escapeHtml($block->getId()) ?>', '<?= /* @escapeNotVerified */ $block->getGridUrl() ?>', '<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameSort() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameDir() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameFilter() ?>'); - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.useAjax = <?= /* @escapeNotVerified */ $block->getUseAjax() ? 'true' : 'false' ?>; - <?php if ($block->getRowClickCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.rowClickCallback = <?= /* @escapeNotVerified */ $block->getRowClickCallback() ?>; + <?= $block->escapeJs($block->getJsObjectName()) ?> = new varienGrid('<?= $block->escapeHtml($block->getId()) ?>', '<?= $block->escapeJs($block->getGridUrl()) ?>', '<?= $block->escapeJs($block->getVarNamePage()) ?>', '<?= $block->escapeJs($block->getVarNameSort()) ?>', '<?= $block->escapeJs($block->getVarNameDir()) ?>', '<?= $block->escapeJs($block->getVarNameFilter()) ?>'); + <?= $block->escapeJs($block->getJsObjectName()) ?>.useAjax = <?= /* @noEscape */ $block->getUseAjax() ? 'true' : 'false' ?>; + <?php if ($block->getRowClickCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.rowClickCallback = <?= /* @noEscape */ $block->getRowClickCallback() ?>; <?php endif; ?> - <?php if ($block->getCheckboxCheckCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.checkboxCheckCallback = <?= /* @escapeNotVerified */ $block->getCheckboxCheckCallback() ?>; + <?php if ($block->getCheckboxCheckCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.checkboxCheckCallback = <?= /* @noEscape */ $block->getCheckboxCheckCallback() ?>; <?php endif; ?> - <?php if ($block->getSortableUpdateCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.sortableUpdateCallback = <?= /* @escapeNotVerified */ $block->getSortableUpdateCallback() ?>; + <?php if ($block->getSortableUpdateCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.sortableUpdateCallback = <?= /* @noEscape */ $block->getSortableUpdateCallback() ?>; <?php endif; ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.bindSortable(); - <?php if ($block->getRowInitCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.initRowCallback = <?= /* @escapeNotVerified */ $block->getRowInitCallback() ?>; - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.initGridRows(); + <?= $block->escapeJs($block->getJsObjectName()) ?>.bindSortable(); + <?php if ($block->getRowInitCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.initRowCallback = <?= /* @noEscape */ $block->getRowInitCallback() ?>; + <?= $block->escapeJs($block->getJsObjectName()) ?>.initGridRows(); <?php endif; ?> - <?php if ($block->getChildBlock('grid.massaction') && $block->getChildBlock('grid.massaction')->isAvailable()): ?> - <?= /* @escapeNotVerified */ $block->getChildBlock('grid.massaction')->getJavaScript() ?> + <?php if ($block->getChildBlock('grid.massaction') && $block->getChildBlock('grid.massaction')->isAvailable()) : ?> + <?= /* @noEscape */ $block->getChildBlock('grid.massaction')->getJavaScript() ?> <?php endif ?> - <?= /* @escapeNotVerified */ $block->getAdditionalJavaScript() ?> + <?= /* @noEscape */ $block->getAdditionalJavaScript() ?> - <?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> }); <?php endif; ?> }); </script> <?php endif; ?> -<?php if ($block->getChildBlock('grid.js')): ?> - <?= $block->getChildHtml('grid.js') ?> -<?php endif; ?> + <?php if ($block->getChildBlock('grid.js')) : ?> + <?= $block->getChildHtml('grid.js') ?> + <?php endif; ?> <?php endif ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/column_set.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/column_set.phtml index 5ff9cfbd96f2c..0906694520cb6 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/column_set.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/column_set.phtml @@ -3,42 +3,39 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** * Template for \Magento\Backend\Block\Widget\Grid\ColumnSet * @var $block \Magento\Backend\Block\Widget\Grid\ColumnSet */ -$numColumns = sizeof($block->getColumns()); +$numColumns = count($block->getColumns()); ?> -<?php if ($block->getCollection()): ?> +<?php if ($block->getCollection()) : ?> <?php /* This part is commented to remove all <col> tags from the code. */ /* foreach ($block->getColumns() as $_column): ?> <col <?= $_column->getHtmlProperty() ?> /> <?php endforeach; */ ?> - <?php if ($block->isHeaderVisible()): ?> + <?php if ($block->isHeaderVisible()) : ?> <thead> - <?php if ($block->isHeaderVisible() || $block->getFilterVisibility()): ?> + <?php if ($block->isHeaderVisible() || $block->getFilterVisibility()) : ?> <tr> - <?php foreach ($block->getColumns() as $_column): ?> - <?php /* @var $_column \Magento\Backend\Block\Widget\Grid\Column */ ?> - <?php if ($_column->getHeaderHtml() == ' '):?> - <th class="data-grid-th" data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" + <?php foreach ($block->getColumns() as $_column) : ?> + <?php /* @var $_column \Magento\Backend\Block\Widget\Grid\Column */ ?> + <?php if ($_column->getHeaderHtml() == ' ') :?> + <th class="data-grid-th" data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" <?= $_column->getHeaderHtmlProperty() ?>> </th> - <?php else: ?> + <?php else : ?> <?= $_column->getHeaderHtml() ?> <?php endif; ?> <?php endforeach; ?> </tr> <?php endif; ?> - <?php if ($block->isFilterVisible()): ?> + <?php if ($block->isFilterVisible()) : ?> <tr class="data-grid-filters" data-role="filter-form"> - <?php $i = 0; foreach ($block->getColumns() as $_column): ?> - <td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" <?= $_column->getHeaderHtmlProperty() ?>> + <?php $i = 0; foreach ($block->getColumns() as $_column) : ?> + <td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" <?= $_column->getHeaderHtmlProperty() ?>> <?= $_column->getFilterHtml() ?> </td> <?php endforeach; ?> @@ -49,75 +46,76 @@ $numColumns = sizeof($block->getColumns()); <tbody> - <?php if ($block->getCollection()->getSize() > 0 && !$block->getIsCollapsed()): ?> - <?php foreach ($block->getCollection() as $_index => $_item): ?> - <?php if ($block->hasMultipleRows($_item)) :?> - <?php $block->updateItemByFirstMultiRow($_item); ?> - <tr title="<?= /* @escapeNotVerified */ $block->getRowUrl($_item) ?>" data-role="row" - <?php if ($_class = $block->getRowClass($_item)):?> class="<?= /* @escapeNotVerified */ $_class ?>"<?php endif;?> - ><?php $i = 0; foreach ($block->getColumns() as $_column): - if ($block->shouldRenderCell($_item, $_column)): + <?php if ($block->getCollection()->getSize() > 0 && !$block->getIsCollapsed()) : ?> + <?php foreach ($block->getCollection() as $_index => $_item) : ?> + <?php if ($block->hasMultipleRows($_item)) :?> + <?php $block->updateItemByFirstMultiRow($_item); ?> + <tr title="<?= $block->escapeHtmlAttr($block->getRowUrl($_item)) ?>" data-role="row" + <?php if ($_class = $block->getRowClass($_item)) :?> class="<?= $block->escapeHtmlAttr($_class) ?>"<?php endif;?> + ><?php $i = 0; foreach ($block->getColumns() as $_column) : + if ($block->shouldRenderCell($_item, $_column)) : $_rowspan = $block->getRowspan($_item, $_column); - ?><td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - <?= ($_rowspan ? 'rowspan="' . $_rowspan . '" ' : '') ?> - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" + ?><td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + <?= /* @noEscape */ ($_rowspan ? 'rowspan="' . $_rowspan . '" ' : '') ?> + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" > - <?= (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> + <?= /* @noEscape */ (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> </td><?php - if ($block->shouldRenderEmptyCell($_item, $_column)):?> - <td colspan="<?= /* @escapeNotVerified */ $block->getEmptyCellColspan($_item) ?>" class="last"> - <?= /* @escapeNotVerified */ $block->getEmptyCellLabel() ?> + if ($block->shouldRenderEmptyCell($_item, $_column)) :?> + <td colspan="<?= $block->escapeHtmlAttr($block->getEmptyCellColspan($_item)) ?>" class="last"> + <?= $block->escapeHtml($block->getEmptyCellLabel()) ?> </td><?php endif; endif; endforeach; - ?></tr> - <?php $_isFirstRow = true; ?> - <?php foreach ($block->getMultipleRows($_item) as $_i):?> - <?php if ($_isFirstRow) : ?> - <?php $_isFirstRow = false; continue; ?> - <?php endif; ?> +?></tr> + <?php $_isFirstRow = true; ?> + <?php foreach ($block->getMultipleRows($_item) as $_i) : ?> + <?php + if ($_isFirstRow) { + $_isFirstRow = false; + continue; + } + ?> <tr data-role="row"> - <?php $i = 0; foreach ($block->getMultipleRowColumns($_i) as $_column): - ?><td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns-1 ? 'last' : '' ?>" + <?php $i = 0; foreach ($block->getMultipleRowColumns($_i) as $_column) : + ?><td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns-1 ? 'last' : '' ?>" > - <?= (($_html = $_column->getRowField($_i)) != '' ? $_html : ' ') ?> + <?= /* @noEscape */ (($_html = $_column->getRowField($_i)) != '' ? $_html : ' ') ?> </td><?php endforeach; ?> </tr> <?php endforeach;?> - <?php if ($block->shouldRenderSubTotal($_item)): ?> + <?php if ($block->shouldRenderSubTotal($_item)) : ?> <tr class="subtotals"> - <?php $i = 0; foreach ($block->getMultipleRowColumns() as $_column): ?> - <td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" + <?php $i = 0; foreach ($block->getMultipleRowColumns() as $_column) : ?> + <td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" > - <?php /* @escapeNotVerified */ echo $_column->hasSubtotalsLabel() ? $_column->getSubtotalsLabel() - : $_column->getRowField($block->getSubTotals($_item)); - ?> + <?= /* @noEscape */ $_column->hasSubtotalsLabel() ? $block->escapeHtml($_column->getSubtotalsLabel()) : $_column->getRowField($block->getSubTotals($_item)) ?> </td> <?php endforeach; ?> </tr> <?php endif; ?> - <?php else: ?> - <tr data-role="row" title="<?= /* @escapeNotVerified */ $block->getRowUrl($_item) ?>"<?php if ($_class = $block->getRowClass($_item)):?> - class="<?= /* @escapeNotVerified */ $_class ?>"<?php endif;?> + <?php else : ?> + <tr data-role="row" title="<?= $block->escapeHtmlAttr($block->getRowUrl($_item)) ?>"<?php if ($_class = $block->getRowClass($_item)) : ?> + class="<?= $block->escapeHtmlAttr($_class) ?>"<?php endif;?> > - <?php $i = 0; foreach ($block->getColumns() as $_column): ?> - <?php if ($block->shouldRenderCell($_item, $_column)):?> - <td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" + <?php $i = 0; foreach ($block->getColumns() as $_column) : ?> + <?php if ($block->shouldRenderCell($_item, $_column)) : ?> + <td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?> <?= ++$i == $numColumns ? 'last' : '' ?>" > - <?= (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> + <?= /* @noEscape */ (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> </td> - <?php if ($block->shouldRenderEmptyCell($_item, $_column)):?> - <td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - colspan="<?= /* @escapeNotVerified */ $block->getEmptyCellColspan($_item) ?>" - class="col-no-records <?= /* @escapeNotVerified */ $block->getEmptyTextClass() ?> last" + <?php if ($block->shouldRenderEmptyCell($_item, $_column)) : ?> + <td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + colspan="<?= $block->escapeHtmlAttr($block->getEmptyCellColspan($_item)) ?>" + class="col-no-records <?= $block->escapeHtmlAttr($block->getEmptyTextClass()) ?> last" > - <?= /* @escapeNotVerified */ $block->getEmptyCellLabel() ?> + <?= $block->escapeHtml($block->getEmptyCellLabel()) ?> </td> <?php endif;?> <?php endif;?> @@ -125,23 +123,22 @@ $numColumns = sizeof($block->getColumns()); </tr> <?php endif; ?> <?php endforeach; ?> - <?php elseif ($block->getEmptyText()): ?> + <?php elseif ($block->getEmptyText()) : ?> <tr class="data-grid-tr-no-data" data-role="row"> - <td class="<?= /* @escapeNotVerified */ $block->getEmptyTextClass() ?>" - colspan="<?= /* @escapeNotVerified */ $numColumns ?>"><?= /* @escapeNotVerified */ $block->getEmptyText() ?></td> + <td class="<?= $block->escapeHtmlAttr($block->getEmptyTextClass()) ?>" + colspan="<?= $block->escapeHtmlAttr($numColumns) ?>"><?= $block->escapeHtml($block->getEmptyText()) ?></td> </tr> <?php endif; ?> </tbody> - <?php if ($block->shouldRenderTotal()): ?> + <?php if ($block->shouldRenderTotal()) : ?> <tfoot> <tr class="totals" data-role="row"> - <?php foreach ($block->getColumns() as $_column): ?> - <th data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?>" + <?php foreach ($block->getColumns() as $_column) : ?> + <th data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?>" > - <?php /* @escapeNotVerified */ echo($_column->hasTotalsLabel()) ? $_column->getTotalsLabel() - : $_column->getRowField($block->getTotals()) ?> + <?= /* @noEscape */ ($_column->hasTotalsLabel()) ? $block->escapeHtml($_column->getTotalsLabel()) : $_column->getRowField($block->getTotals()) ?> </th> <?php endforeach; ?> </tr> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container.phtml index 8a40853a405b0..6a8ec2a934ca4 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container.phtml @@ -3,11 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if ($block->getButtonsHtml()): ?> +<?php if ($block->getButtonsHtml()) : ?> <div data-mage-init='{"floatingHeader": {}}' class="page-actions"><?= $block->getButtonsHtml() ?></div> <?php endif; ?> <?= $block->getGridHtml() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container/empty.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container/empty.phtml index 700e9749f734b..e62c6ca3b4255 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container/empty.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/container/empty.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getGridHtml() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/export.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/export.phtml index 35be61dad7f73..e70b93d0afc10 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/export.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/export.phtml @@ -3,17 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div class="admin__data-grid-export"> - <label for="<?= /* @escapeNotVerified */ $block->getId() ?>_export" class="admin__control-support-text"> - <?= /* @escapeNotVerified */ __('Export to:') ?> + <label for="<?= $block->escapeHtmlAttr($block->getId()) ?>_export" class="admin__control-support-text"> + <?= $block->escapeHtml(__('Export to:')) ?> </label> - <select name="<?= /* @escapeNotVerified */ $block->getId() ?>_export" id="<?= /* @escapeNotVerified */ $block->getId() ?>_export" class="admin__control-select"> - <?php foreach ($block->getExportTypes() as $_type): ?> - <option value="<?= /* @escapeNotVerified */ $_type->getUrl() ?>"><?= /* @escapeNotVerified */ $_type->getLabel() ?></option> + <select name="<?= $block->escapeHtmlAttr($block->getId()) ?>_export" id="<?= $block->escapeHtmlAttr($block->getId()) ?>_export" class="admin__control-select"> + <?php foreach ($block->getExportTypes() as $_type) : ?> + <option value="<?= $block->escapeHtmlAttr($_type->getUrl()) ?>"><?= $block->escapeHtml($_type->getLabel()) ?></option> <?php endforeach; ?> </select> <?= $block->getExportButtonHtml() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml index f97db4ad993b1..0bb453f25d7ca 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -17,35 +14,35 @@ * getPagerVisibility() * getVarNamePage() */ -$numColumns = sizeof($block->getColumns()); +$numColumns = count($block->getColumns()); /** * @var \Magento\Backend\Block\Widget\Grid\Extended $block */ ?> -<?php if ($block->getCollection()): ?> - <?php if ($block->canDisplayContainer()): ?> +<?php if ($block->getCollection()) : ?> + <?php if ($block->canDisplayContainer()) : ?> <div id="<?= $block->escapeHtml($block->getId()) ?>" data-grid-id="<?= $block->escapeHtml($block->getId()) ?>"> - <?php else: ?> + <?php else : ?> <?= $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> <?php endif; ?> <?php $massActionAvailable = $block->getMassactionBlock() && $block->getMassactionBlock()->isAvailable() ?> - <?php if ($block->getPagerVisibility() || $block->getExportTypes() || $block->getFilterVisibility() || $massActionAvailable): ?> + <?php if ($block->getPagerVisibility() || $block->getExportTypes() || $block->getFilterVisibility() || $massActionAvailable) : ?> <div class="admin__data-grid-header admin__data-grid-toolbar"> <div class="admin__data-grid-header-row"> - <?php if ($massActionAvailable): ?> + <?php if ($massActionAvailable) : ?> <?= $block->getMainButtonsHtml() ? '<div class="admin__filter-actions">' . $block->getMainButtonsHtml() . '</div>' : '' ?> <?php endif; ?> - <?php if ($block->getExportTypes()): ?> + <?php if ($block->getExportTypes()) : ?> <div class="admin__data-grid-export"> <label class="admin__control-support-text" - for="<?= $block->escapeHtml($block->getId()) ?>_export"><?= /* @escapeNotVerified */ __('Export to:') ?></label> + for="<?= $block->escapeHtml($block->getId()) ?>_export"><?= $block->escapeHtml(__('Export to:')) ?></label> <select name="<?= $block->escapeHtml($block->getId()) ?>_export" id="<?= $block->escapeHtml($block->getId()) ?>_export" class="admin__control-select"> - <?php foreach ($block->getExportTypes() as $_type): ?> - <option value="<?= /* @escapeNotVerified */ $_type->getUrl() ?>"><?= /* @escapeNotVerified */ $_type->getLabel() ?></option> + <?php foreach ($block->getExportTypes() as $_type) : ?> + <option value="<?= $block->escapeHtmlAttr($_type->getUrl()) ?>"><?= $block->escapeHtml($_type->getLabel()) ?></option> <?php endforeach; ?> </select> <?= $block->getExportButtonHtml() ?> @@ -54,76 +51,76 @@ $numColumns = sizeof($block->getColumns()); </div> <div class="admin__data-grid-header-row <?= $massActionAvailable ? '_massaction' : '' ?>"> - <?php if ($massActionAvailable): ?> + <?php if ($massActionAvailable) : ?> <?= $block->getMassactionBlockHtml() ?> - <?php else: ?> + <?php else : ?> <?= $block->getMainButtonsHtml() ? '<div class="admin__filter-actions">' . $block->getMainButtonsHtml() . '</div>' : '' ?> <?php endif; ?> <?php $countRecords = $block->getCollection()->getSize(); ?> <div class="admin__control-support-text"> - <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?= /* @escapeNotVerified */ $block->getUiId('total-count') ?>> - <?= /* @escapeNotVerified */ $countRecords ?> + <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?= /* @noEscape */ $block->getUiId('total-count') ?>> + <?= /* @noEscape */ $countRecords ?> </span> - <?= /* @escapeNotVerified */ __('records found') ?> + <?= $block->escapeHtml(__('records found')) ?> <span id="<?= $block->escapeHtml($block->getHtmlId()) ?>_massaction-count" - class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?= /* @escapeNotVerified */ __('selected') ?></span></span> + class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?= $block->escapeHtml(__('selected')) ?></span></span> </div> - <?php if ($block->getPagerVisibility()): ?> + <?php if ($block->getPagerVisibility()) : ?> <div class="admin__data-grid-pager-wrap"> - <select name="<?= /* @escapeNotVerified */ $block->getVarNameLimit() ?>" + <select name="<?= $block->escapeHtmlAttr($block->getVarNameLimit()) ?>" id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" - onchange="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.loadByElement(this)" + onchange="<?= /* @noEscape */ $block->getJsObjectName() ?>.loadByElement(this)" class="admin__control-select"> - <option value="20"<?php if ($block->getCollection()->getPageSize() == 20): ?> + <option value="20"<?php if ($block->getCollection()->getPageSize() == 20) : ?> selected="selected"<?php endif; ?>>20 </option> - <option value="30"<?php if ($block->getCollection()->getPageSize() == 30): ?> + <option value="30"<?php if ($block->getCollection()->getPageSize() == 30) : ?> selected="selected"<?php endif; ?>>30 </option> - <option value="50"<?php if ($block->getCollection()->getPageSize() == 50): ?> + <option value="50"<?php if ($block->getCollection()->getPageSize() == 50) : ?> selected="selected"<?php endif; ?>>50 </option> - <option value="100"<?php if ($block->getCollection()->getPageSize() == 100): ?> + <option value="100"<?php if ($block->getCollection()->getPageSize() == 100) : ?> selected="selected"<?php endif; ?>>100 </option> - <option value="200"<?php if ($block->getCollection()->getPageSize() == 200): ?> + <option value="200"<?php if ($block->getCollection()->getPageSize() == 200) : ?> selected="selected"<?php endif; ?>>200 </option> </select> <label for="<?= $block->escapeHtml($block->getHtmlId()) ?><?= $block->escapeHtml($block->getHtmlId()) ?>_page-limit" - class="admin__control-support-text"><?= /* @escapeNotVerified */ __('per page') ?></label> + class="admin__control-support-text"><?= $block->escapeHtml(__('per page')) ?></label> <div class="admin__data-grid-pager"> <?php $_curPage = $block->getCollection()->getCurPage() ?> <?php $_lastPage = $block->getCollection()->getLastPageNumber() ?> - <?php if ($_curPage > 1): ?> + <?php if ($_curPage > 1) : ?> <button class="action-previous" type="button" - onclick="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setPage('<?= /* @escapeNotVerified */ ($_curPage - 1) ?>');return false;"> - <span><?= /* @escapeNotVerified */ __('Previous page') ?></span> + onclick="<?= /* @noEscape */ $block->getJsObjectName() ?>.setPage('<?= /* @noEscape */ ($_curPage - 1) ?>');return false;"> + <span><?= $block->escapeHtml(__('Previous page')) ?></span> </button> - <?php else: ?> - <button type="button" class="action-previous disabled"><span><?= /* @escapeNotVerified */ __('Previous page') ?></span></button> + <?php else : ?> + <button type="button" class="action-previous disabled"><span><?= $block->escapeHtml(__('Previous page')) ?></span></button> <?php endif; ?> <input type="text" id="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current" - name="<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>" - value="<?= /* @escapeNotVerified */ $_curPage ?>" + name="<?= $block->escapeHtmlAttr($block->getVarNamePage()) ?>" + value="<?= $block->escapeHtmlAttr($_curPage) ?>" class="admin__control-text" - onkeypress="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.inputPage(event, '<?= /* @escapeNotVerified */ $_lastPage ?>')" <?= /* @escapeNotVerified */ $block->getUiId('current-page') ?> /> + onkeypress="<?= /* @noEscape */ $block->getJsObjectName() ?>.inputPage(event, '<?= /* @noEscape */ $_lastPage ?>')" <?= /* @noEscape */ $block->getUiId('current-page') ?> /> <label class="admin__control-support-text" for="<?= $block->escapeHtml($block->getHtmlId()) ?>_page-current"> - <?= /* @escapeNotVerified */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> + <?= /* @noEscape */ __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> - <?php if ($_curPage < $_lastPage): ?> + <?php if ($_curPage < $_lastPage) : ?> <button type="button" - title="<?= /* @escapeNotVerified */ __('Next page') ?>" + title="<?= $block->escapeHtmlAttr(__('Next page')) ?>" class="action-next" - onclick="<?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setPage('<?= /* @escapeNotVerified */ ($_curPage + 1) ?>');return false;"> - <span><?= /* @escapeNotVerified */ __('Next page') ?></span> + onclick="<?= /* @noEscape */ $block->getJsObjectName() ?>.setPage('<?= /* @noEscape */ ($_curPage + 1) ?>');return false;"> + <span><?= $block->escapeHtml(__('Next page')) ?></span> </button> - <?php else: ?> - <button type="button" class="action-next disabled"><span><?= /* @escapeNotVerified */ __('Next page') ?></span></button> + <?php else : ?> + <button type="button" class="action-next disabled"><span><?= $block->escapeHtml(__('Next page')) ?></span></button> <?php endif; ?> </div> </div> @@ -140,25 +137,25 @@ $numColumns = sizeof($block->getColumns()); <col <?= $_column->getHtmlProperty() ?> /> <?php endforeach; */ ?> - <?php if ($block->getHeadersVisibility() || $block->getFilterVisibility()): ?> + <?php if ($block->getHeadersVisibility() || $block->getFilterVisibility()) : ?> <thead> - <?php if ($block->getHeadersVisibility()): ?> + <?php if ($block->getHeadersVisibility()) : ?> <tr> - <?php foreach ($block->getColumns() as $_column): ?> - <?php if ($_column->getHeaderHtml() == ' '):?> - <th class="data-grid-th" data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" + <?php foreach ($block->getColumns() as $_column) : ?> + <?php if ($_column->getHeaderHtml() == ' ') : ?> + <th class="data-grid-th" data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" <?= $_column->getHeaderHtmlProperty() ?>> </th> - <?php else: ?> + <?php else : ?> <?= $_column->getHeaderHtml() ?> <?php endif; ?> <?php endforeach; ?> </tr> <?php endif; ?> - <?php if ($block->getFilterVisibility()): ?> + <?php if ($block->getFilterVisibility()) : ?> <tr class="data-grid-filters" data-role="filter-form"> <?php $i = 0; - foreach ($block->getColumns() as $_column): ?> - <td data-column="<?= /* @escapeNotVerified */ $_column->getId() ?>" <?= $_column->getHeaderHtmlProperty() ?>> + foreach ($block->getColumns() as $_column) : ?> + <td data-column="<?= $block->escapeHtmlAttr($_column->getId()) ?>" <?= $_column->getHeaderHtmlProperty() ?>> <?= $_column->getFilterHtml() ?> </td> <?php endforeach; ?> @@ -166,12 +163,12 @@ $numColumns = sizeof($block->getColumns()); <?php endif ?> </thead> <?php endif; ?> - <?php if ($block->getCountTotals()): ?> + <?php if ($block->getCountTotals()) : ?> <tfoot> <tr class="totals"> - <?php foreach ($block->getColumns() as $_column): ?> - <th class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?>"> - <?= /* @escapeNotVerified */ ($_column->hasTotalsLabel()) ? $_column->getTotalsLabel() : $_column->getRowField($_column->getGrid()->getTotals()) ?> + <?php foreach ($block->getColumns() as $_column) : ?> + <th class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?>"> + <?= /* @noEscape */ ($_column->hasTotalsLabel()) ? $block->escapeHtml($_column->getTotalsLabel()) : $_column->getRowField($_column->getGrid()->getTotals()) ?> </th> <?php endforeach; ?> </tr> @@ -179,116 +176,114 @@ $numColumns = sizeof($block->getColumns()); <?php endif; ?> <tbody> - <?php if (($block->getCollection()->getSize() > 0) && (!$block->getIsCollapsed())): ?> - <?php foreach ($block->getCollection() as $_index => $_item): ?> - <tr title="<?= /* @escapeNotVerified */ $block->getRowUrl($_item) ?>"<?php if ($_class = $block->getRowClass($_item)): ?> - class="<?= /* @escapeNotVerified */ $_class ?>"<?php endif; ?> ><?php + <?php if (($block->getCollection()->getSize() > 0) && (!$block->getIsCollapsed())) : ?> + <?php foreach ($block->getCollection() as $_index => $_item) : ?> + <tr title="<?= $block->escapeHtmlAttr($block->getRowUrl($_item)) ?>"<?php if ($_class = $block->getRowClass($_item)) : ?> + class="<?= $block->escapeHtmlAttr($_class) ?>"<?php endif; ?> ><?php $i = 0; - foreach ($block->getColumns() as $_column): - if ($block->shouldRenderCell($_item, $_column)): + foreach ($block->getColumns() as $_column) : + if ($block->shouldRenderCell($_item, $_column)) : $_rowspan = $block->getRowspan($_item, $_column); ?> - <td <?= ($_rowspan ? 'rowspan="' . $_rowspan . '" ' : '') ?> - class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> - <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> - <?= (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> + <td <?= /* @noEscape */ ($_rowspan ? 'rowspan="' . $_rowspan . '" ' : '') ?> + class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> + <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> + <?= /* @noEscape */ (($_html = $_column->getRowField($_item)) != '' ? $_html : ' ') ?> </td><?php - if ($block->shouldRenderEmptyCell($_item, $_column)): + if ($block->shouldRenderEmptyCell($_item, $_column)) : ?> - <td colspan="<?= /* @escapeNotVerified */ $block->getEmptyCellColspan($_item) ?>" - class="last"><?= /* @escapeNotVerified */ $block->getEmptyCellLabel() ?></td><?php + <td colspan="<?= $block->escapeHtmlAttr($block->getEmptyCellColspan($_item)) ?>" + class="last"><?= $block->escapeHtml($block->getEmptyCellLabel()) ?></td><?php endif; endif; endforeach; ?> </tr> - <?php if ($_multipleRows = $block->getMultipleRows($_item)): ?> - <?php foreach ($_multipleRows as $_i): ?> + <?php if ($_multipleRows = $block->getMultipleRows($_item)) : ?> + <?php foreach ($_multipleRows as $_i) : ?> <tr> <?php $i = 0; - foreach ($block->getMultipleRowColumns($_i) as $_column): ?> - <td class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> - <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> - <?= (($_html = $_column->getRowField($_i)) != '' ? $_html : ' ') ?> + foreach ($block->getMultipleRowColumns($_i) as $_column) : ?> + <td class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> + <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> + <?= /* @noEscape */ (($_html = $_column->getRowField($_i)) != '' ? $_html : ' ') ?> </td> <?php endforeach; ?> </tr> <?php endforeach; ?> <?php endif; ?> - <?php if ($block->shouldRenderSubTotal($_item)): ?> + <?php if ($block->shouldRenderSubTotal($_item)) : ?> <tr class="subtotals"> <?php $i = 0; - foreach ($block->getSubTotalColumns() as $_column): ?> - <td class="<?= /* @escapeNotVerified */ $_column->getCssProperty() ?> - <?= /* @escapeNotVerified */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> - <?php /* @escapeNotVerified */ echo($_column->hasSubtotalsLabel() ? $_column->getSubtotalsLabel() : - $_column->getRowField($block->getSubTotalItem($_item)) - ); - ?> + foreach ($block->getSubTotalColumns() as $_column) : ?> + <td class="<?= $block->escapeHtmlAttr($_column->getCssProperty()) ?> + <?= /* @noEscape */ $_column->getId() == 'massaction' ? 'data-grid-checkbox-cell': '' ?>"> + <?= /* @noEscape */ $_column->hasSubtotalsLabel() ? $block->escapeHtml($_column->getSubtotalsLabel()) : $_column->getRowField($block->getSubTotalItem($_item)) ?> </td> <?php endforeach; ?> </tr> <?php endif; ?> <?php endforeach; ?> - <?php elseif ($block->getEmptyText()): ?> + <?php elseif ($block->getEmptyText()) : ?> <tr class="data-grid-tr-no-data"> - <td class="<?= /* @escapeNotVerified */ $block->getEmptyTextClass() ?>" - colspan="<?= /* @escapeNotVerified */ $numColumns ?>"><?= /* @escapeNotVerified */ $block->getEmptyText() ?></td> + <td class="<?= $block->escapeHtmlAttr($block->getEmptyTextClass()) ?>" + colspan="<?= $block->escapeHtmlAttr($numColumns) ?>"><?= $block->escapeHtml($block->getEmptyText()) ?></td> </tr> <?php endif; ?> </tbody> </table> </div> - <?php if ($block->canDisplayContainer()): ?> + <?php if ($block->canDisplayContainer()) : ?> </div> <script> var deps = []; - <?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> deps.push('uiRegistry'); - <?php endif; ?> + <?php endif; ?> - <?php if (strpos($block->getRowClickCallback(), 'order.') !== false): ?> + <?php if (strpos($block->getRowClickCallback(), 'order.') !== false) : ?> deps.push('Magento_Sales/order/create/form') - <?php endif; ?> + <?php endif; ?> deps.push('mage/adminhtml/grid'); - <?php if (is_array($block->getRequireJsDependencies())): ?> - <?php foreach ($block->getRequireJsDependencies() as $dependency): ?> - deps.push('<?= /* @escapeNotVerified */ $dependency ?>'); - <?php endforeach; ?> - <?php endif; ?> + <?php if (is_array($block->getRequireJsDependencies())) : ?> + <?php foreach ($block->getRequireJsDependencies() as $dependency) : ?> + deps.push('<?= $block->escapeJs($dependency) ?>'); + <?php endforeach; ?> + <?php endif; ?> require(deps, function(<?= ($block->getDependencyJsObject() ? 'registry' : '') ?>){ <?php //TODO: getJsObjectName and getRowClickCallback has unexpected behavior. Should be removed ?> //<![CDATA[ - <?php if ($block->getDependencyJsObject()): ?> - registry.get('<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>', function (<?= /* @escapeNotVerified */ $block->getDependencyJsObject() ?>) { - <?php endif; ?> - - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?> = new varienGrid(<?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getId()) ?>, '<?= /* @escapeNotVerified */ $block->getGridUrl() ?>', '<?= /* @escapeNotVerified */ $block->getVarNamePage() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameSort() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameDir() ?>', '<?= /* @escapeNotVerified */ $block->getVarNameFilter() ?>'); - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.useAjax = '<?= /* @escapeNotVerified */ $block->getUseAjax() ?>'; - <?php if ($block->getRowClickCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.rowClickCallback = <?= /* @escapeNotVerified */ $block->getRowClickCallback() ?>; - <?php endif; ?> - <?php if ($block->getCheckboxCheckCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.checkboxCheckCallback = <?= /* @escapeNotVerified */ $block->getCheckboxCheckCallback() ?>; - <?php endif; ?> - <?php if ($block->getRowInitCallback()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.initRowCallback = <?= /* @escapeNotVerified */ $block->getRowInitCallback() ?>; - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.initGridRows(); - <?php endif; ?> - <?php if ($block->getMassactionBlock() && $block->getMassactionBlock()->isAvailable()): ?> - <?= /* @escapeNotVerified */ $block->getMassactionBlock()->getJavaScript() ?> - <?php endif ?> - <?= /* @escapeNotVerified */ $block->getAdditionalJavaScript() ?> + <?php if ($block->getDependencyJsObject()) : ?> + registry.get('<?= $block->escapeJs($block->getDependencyJsObject()) ?>', function (<?= $block->escapeJs($block->getDependencyJsObject()) ?>) { + <?php endif; ?> + <?php // phpcs:disable ?> + <?= $block->escapeJs($block->getJsObjectName()) ?> = new varienGrid(<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getId()) ?>, '<?= $block->escapeJs($block->getGridUrl()) ?>', '<?= $block->escapeJs($block->getVarNamePage()) ?>', '<?= $block->escapeJs($block->getVarNameSort()) ?>', '<?= $block->escapeJs($block->getVarNameDir()) ?>', '<?= $block->escapeJs($block->getVarNameFilter()) ?>'); + <?php //phpcs:enable ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.useAjax = '<?= $block->escapeJs($block->getUseAjax()) ?>'; + <?php if ($block->getRowClickCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.rowClickCallback = <?= /* @noEscape */ $block->getRowClickCallback() ?>; + <?php endif; ?> + <?php if ($block->getCheckboxCheckCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.checkboxCheckCallback = <?= /* @noEscape */ $block->getCheckboxCheckCallback() ?>; + <?php endif; ?> + <?php if ($block->getRowInitCallback()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.initRowCallback = <?= /* @noEscape */ $block->getRowInitCallback() ?>; + <?= $block->escapeJs($block->getJsObjectName()) ?>.initGridRows(); + <?php endif; ?> + <?php if ($block->getMassactionBlock() && $block->getMassactionBlock()->isAvailable()) : ?> + <?= /* @noEscape */ $block->getMassactionBlock()->getJavaScript() ?> + <?php endif ?> + <?= /* @noEscape */ $block->getAdditionalJavaScript() ?> - <?php if ($block->getDependencyJsObject()): ?> + <?php if ($block->getDependencyJsObject()) : ?> }); - <?php endif; ?> + <?php endif; ?> //]]> }); diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction.phtml index ea995d9a80b28..9a21cd4ef71a1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction.phtml @@ -3,14 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?= /* @escapeNotVerified */ $block->getSomething() ?> +<?= /* @noEscape */ $block->getSomething() ?> <div id="<?= $block->getHtmlId() ?>" class="admin__grid-massaction"> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) : ?> <form action="" id="<?= $block->getHtmlId() ?>-form" method="post"> <?php endif ?> <div class="admin__grid-massaction-form"> @@ -18,23 +15,23 @@ <select id="<?= $block->getHtmlId() ?>-select" class="required-entry local-validation admin__control-select" - <?= /* @escapeNotVerified */ $block->getUiId('select') ?>> - <option class="admin__control-select-placeholder" value="" selected><?= /* @escapeNotVerified */ __('Actions') ?></option> - <?php foreach ($block->getItems() as $_item):?> - <option value="<?= /* @escapeNotVerified */ $_item->getId() ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= /* @escapeNotVerified */ $_item->getLabel() ?></option> + <?= /* @noEscape */ $block->getUiId('select') ?>> + <option class="admin__control-select-placeholder" value="" selected><?= $block->escapeHtml(__('Actions')) ?></option> + <?php foreach ($block->getItems() as $_item) : ?> + <option value="<?= $block->escapeHtmlAttr($_item->getId()) ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= $block->escapeHtml($_item->getLabel()) ?></option> <?php endforeach; ?> </select> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-hiddens"></span> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-additional"></span> <?= $block->getApplyButtonHtml() ?> </div> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) :?> </form> <?php endif ?> <div class="no-display"> - <?php foreach ($block->getItems() as $_item): ?> - <div id="<?= $block->getHtmlId() ?>-item-<?= /* @escapeNotVerified */ $_item->getId() ?>-block"> - <?php if ('' != $_item->getBlockName()):?> + <?php foreach ($block->getItems() as $_item) : ?> + <div id="<?= $block->getHtmlId() ?>-item-<?= /* @noEscape */ $_item->getId() ?>-block"> + <?php if ('' != $_item->getBlockName()) :?> <?= $block->getChildHtml($_item->getBlockName()) ?> <?php endif;?> </div> @@ -47,21 +44,21 @@ class="action-select-multiselect _disabled" disabled="disabled" data-menu="grid-mass-select"> - <optgroup label="<?= /* @escapeNotVerified */ __('Mass Actions') ?>"> + <optgroup label="<?= $block->escapeHtmlAttr(__('Mass Actions')) ?>"> <option disabled selected></option> - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) :?> <option value="selectAll"> - <?= /* @escapeNotVerified */ __('Select All') ?> + <?= $block->escapeHtml(__('Select All')) ?> </option> <option value="unselectAll"> - <?= /* @escapeNotVerified */ __('Unselect All') ?> + <?= $block->escapeHtml(__('Unselect All')) ?> </option> <?php endif; ?> <option value="selectVisible"> - <?= /* @escapeNotVerified */ __('Select Visible') ?> + <?= $block->escapeHtml(__('Select Visible')) ?> </option> <option value="unselectVisible"> - <?= /* @escapeNotVerified */ __('Unselect Visible') ?> + <?= $block->escapeHtml(__('Unselect Visible')) ?> </option> </optgroup> </select> @@ -78,25 +75,25 @@ var massAction = $('option:selected', this).val(); this.blur(); switch (massAction) { - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) : ?> case 'selectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectAll(); break; case 'unselectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectAll(); break; <?php endif; ?> case 'selectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectVisible(); break; case 'unselectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectVisible(); break; } }); }); - <?php if (!$block->getParentBlock()->canDisplayContainer()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setGridIds('<?= /* @escapeNotVerified */ $block->getGridIdsJson() ?>'); + <?php if (!$block->getParentBlock()->canDisplayContainer()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.setGridIds('<?= $block->escapeJs($block->getGridIdsJson()) ?>'); <?php endif; ?> </script> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction_extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction_extended.phtml index f969fa61097ae..c0f30fc282f38 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction_extended.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/massaction_extended.phtml @@ -3,13 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div id="<?= $block->getHtmlId() ?>" class="admin__grid-massaction"> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) : ?> <form action="" id="<?= $block->getHtmlId() ?>-form" method="post"> <?php endif ?> <div class="admin__grid-massaction-form"> @@ -17,21 +14,21 @@ <select id="<?= $block->getHtmlId() ?>-select" class="required-entry local-validation admin__control-select"> - <option class="admin__control-select-placeholder" value="" selected><?= /* @escapeNotVerified */ __('Actions') ?></option> - <?php foreach ($block->getItems() as $_item): ?> - <option value="<?= /* @escapeNotVerified */ $_item->getId() ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= /* @escapeNotVerified */ $_item->getLabel() ?></option> + <option class="admin__control-select-placeholder" value="" selected><?= $block->escapeHtml(__('Actions')) ?></option> + <?php foreach ($block->getItems() as $_item) : ?> + <option value="<?= $block->escapeHtmlAttr($_item->getId()) ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= $block->escapeHtml($_item->getLabel()) ?></option> <?php endforeach; ?> </select> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-hiddens"></span> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-additional"></span> <?= $block->getApplyButtonHtml() ?> </div> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) : ?> </form> <?php endif ?> <div class="no-display"> - <?php foreach ($block->getItems() as $_item): ?> - <div id="<?= $block->getHtmlId() ?>-item-<?= /* @escapeNotVerified */ $_item->getId() ?>-block"> + <?php foreach ($block->getItems() as $_item) : ?> + <div id="<?= $block->getHtmlId() ?>-item-<?= /* @noEscape */ $_item->getId() ?>-block"> <?= $_item->getAdditionalActionBlockHtml() ?> </div> <?php endforeach; ?> @@ -41,21 +38,21 @@ id="<?= $block->getHtmlId() ?>-mass-select" class="action-select-multiselect" data-menu="grid-mass-select"> - <optgroup label="<?= /* @escapeNotVerified */ __('Mass Actions') ?>"> + <optgroup label="<?= $block->escapeHtml(__('Mass Actions')) ?>"> <option disabled selected></option> - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) : ?> <option value="selectAll"> - <?= /* @escapeNotVerified */ __('Select All') ?> + <?= $block->escapeHtml(__('Select All')) ?> </option> <option value="unselectAll"> - <?= /* @escapeNotVerified */ __('Unselect All') ?> + <?= $block->escapeHtml(__('Unselect All')) ?> </option> <?php endif; ?> <option value="selectVisible"> - <?= /* @escapeNotVerified */ __('Select Visible') ?> + <?= $block->escapeHtml(__('Select Visible')) ?> </option> <option value="unselectVisible"> - <?= /* @escapeNotVerified */ __('Unselect Visible') ?> + <?= $block->escapeHtml(__('Unselect Visible')) ?> </option> </optgroup> </select> @@ -67,27 +64,27 @@ $('#<?= $block->getHtmlId() ?>-mass-select').change(function () { var massAction = $('option:selected', this).val(); switch (massAction) { - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) : ?> case 'selectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectAll(); break; case 'unselectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectAll(); break; <?php endif; ?> case 'selectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectVisible(); break; case 'unselectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectVisible(); break; } this.blur(); }); }); - <?php if (!$block->getParentBlock()->canDisplayContainer()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setGridIds('<?= /* @escapeNotVerified */ $block->getGridIdsJson() ?>'); + <?php if (!$block->getParentBlock()->canDisplayContainer()) : ?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.setGridIds('<?= /* @noEscape */ $block->getGridIdsJson() ?>'); <?php endif; ?> </script> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/serializer.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/serializer.phtml index 70e2a87987924..2208a00929592 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/serializer.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/serializer.phtml @@ -3,18 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** * @var $block \Magento\Backend\Block\Widget\Grid\Serializer */ ?> -<?php $_id = 'id_' . md5(microtime()) ?> +<?php +// phpcs:ignore +$_id = 'id_' . md5(microtime()); +?> <?php $formId = $block->getFormId()?> -<?php if (!empty($formId)) :?> +<?php if (!empty($formId)) : ?> <script> require([ 'prototype', @@ -23,11 +23,11 @@ Event.observe(window, "load", function(){ var serializeInput = document.createElement('input'); serializeInput.type = 'hidden'; - serializeInput.name = '<?= /* @escapeNotVerified */ $block->getInputElementName() ?>'; - serializeInput.id = '<?= /* @escapeNotVerified */ $_id ?>'; + serializeInput.name = '<?= $block->escapeJs($block->getInputElementName()) ?>'; + serializeInput.id = '<?= /* @noEscape */ $_id ?>'; try { - document.getElementById('<?= /* @escapeNotVerified */ $formId ?>').appendChild(serializeInput); - new serializerController('<?= /* @escapeNotVerified */ $_id ?>', <?= /* @escapeNotVerified */ $block->getDataAsJSON() ?>, <?= /* @escapeNotVerified */ $block->getColumnInputNames(true) ?>, <?= /* @escapeNotVerified */ $block->getGridBlock()->getJsObjectName() ?>, '<?= /* @escapeNotVerified */ $block->getReloadParamName() ?>'); + document.getElementById('<?= $block->escapeJs($formId) ?>').appendChild(serializeInput); + new serializerController('<?= /* @noEscape */ $_id ?>', <?= /* @noEscape */ $block->getDataAsJSON() ?>, <?= /* @noEscape */ $block->getColumnInputNames(true) ?>, <?= $block->escapeJs($block->getGridBlock()->getJsObjectName()) ?>, '<?= $block->escapeJs($block->getReloadParamName()) ?>'); } catch(e) { //Error add serializer } @@ -35,12 +35,12 @@ }); </script> <?php else :?> -<input type="hidden" name="<?= /* @escapeNotVerified */ $block->getInputElementName() ?>" value="" id="<?= /* @escapeNotVerified */ $_id ?>" /> +<input type="hidden" name="<?= $block->escapeHtmlAttr($block->getInputElementName()) ?>" value="" id="<?= /* @noEscape */ $_id ?>" /> <script> require([ 'mage/adminhtml/grid' ], function(){ - new serializerController('<?= /* @escapeNotVerified */ $_id ?>', <?= /* @escapeNotVerified */ $block->getDataAsJSON() ?>, <?= /* @escapeNotVerified */ $block->getColumnInputNames(true) ?>, <?= /* @escapeNotVerified */ $block->getGridBlock()->getJsObjectName() ?>, '<?= /* @escapeNotVerified */ $block->getReloadParamName() ?>'); + new serializerController('<?= /* @noEscape */ $_id ?>', <?= /* @noEscape */ $block->getDataAsJSON() ?>, <?= /* @noEscape */ $block->getColumnInputNames(true) ?>, <?= $block->escapeJs($block->getGridBlock()->getJsObjectName()) ?>, '<?= $block->escapeJs($block->getReloadParamName()) ?>'); }); </script> <?php endif;?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabs.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabs.phtml index 287028f9a1122..5246aac088a5b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabs.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabs.phtml @@ -4,46 +4,47 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Backend\Block\Widget\Tabs */ ?> -<?php if (!empty($tabs)): ?> +<?php if (!empty($tabs)) : ?> -<div class="admin__page-nav" data-role="container" id="<?= /* @escapeNotVerified */ $block->getId() ?>"> - <?php if ($block->getTitle()): ?> - <div class="admin__page-nav-title" data-role="title" <?= /* @escapeNotVerified */ $block->getUiId('title') ?>> - <strong><?= /* @escapeNotVerified */ $block->getTitle() ?></strong> +<div class="admin__page-nav" data-role="container" id="<?= $block->escapeHtmlAttr($block->getId()) ?>"> + <?php if ($block->getTitle()) : ?> + <div class="admin__page-nav-title" data-role="title" <?= /* @noEscape */ $block->getUiId('title') ?>> + <strong><?= $block->escapeHtml($block->getTitle()) ?></strong> <span data-role="title-messages" class="admin__page-nav-title-messages"></span> </div> <?php endif ?> - <ul <?= /* @escapeNotVerified */ $block->getUiId('tab', $block->getId()) ?> class="<?= /* @escapeNotVerified */ $block->getIsHoriz() ? 'tabs-horiz' : 'tabs admin__page-nav-items' ?>"> - <?php foreach ($tabs as $_tab): ?> - - <?php if (!$block->canShowTab($_tab)): continue; endif; ?> + <ul <?= /* @noEscape */ $block->getUiId('tab', $block->getId()) ?> class="<?= /* @noEscape */ $block->getIsHoriz() ? 'tabs-horiz' : 'tabs admin__page-nav-items' ?>"> + <?php foreach ($tabs as $_tab) : ?> + <?php + if (!$block->canShowTab($_tab)) : + continue; + endif; + ?> <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> - <li class="admin__page-nav-item" <?php if ($block->getTabIsHidden($_tab)): ?> style="display:none"<?php endif; ?><?= /* @escapeNotVerified */ $block->getUiId('tab', 'item', $_tab->getId()) ?>> - <a href="<?= /* @escapeNotVerified */ $_tabHref ?>" id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>" name="<?= /* @escapeNotVerified */ $block->getTabId($_tab, false) ?>" title="<?= /* @escapeNotVerified */ $block->getTabTitle($_tab) ?>" - class="admin__page-nav-link <?= /* @escapeNotVerified */ $_tabClass ?>" - data-tab-type="<?= /* @escapeNotVerified */ $_tabType ?>" - <?= /* @escapeNotVerified */ $block->getUiId('tab', 'link', $_tab->getId()) ?>> + <li class="admin__page-nav-item" <?php if ($block->getTabIsHidden($_tab)) : ?> style="display:none"<?php endif; ?><?= /* @noEscape */ $block->getUiId('tab', 'item', $_tab->getId()) ?>> + <a href="<?= $block->escapeUrl($_tabHref) ?>" id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>" name="<?= $block->escapeHtmlAttr($block->getTabId($_tab, false)) ?>" title="<?= $block->escapeHtmlAttr($block->getTabTitle($_tab)) ?>" + class="admin__page-nav-link <?= $block->escapeHtmlAttr($_tabClass) ?>" + data-tab-type="<?= $block->escapeHtmlAttr($_tabType) ?>" + <?= /* @noEscape */ $block->getUiId('tab', 'link', $_tab->getId()) ?>> - <span><?= /* @escapeNotVerified */ $block->getTabLabel($_tab) ?></span> + <span><?= $block->escapeHtml($block->getTabLabel($_tab)) ?></span> <span class="admin__page-nav-item-messages" data-role="item-messages"> <span class="admin__page-nav-item-message _changed"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip"> - <?= /* @escapeNotVerified */ __('Changes have been made to this section that have not been saved.') ?> + <?= $block->escapeHtml(__('Changes have been made to this section that have not been saved.')) ?> </span> </span> <span class="admin__page-nav-item-message _error"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip"> - <?= /* @escapeNotVerified */ __('This tab contains invalid data. Please resolve this before saving.') ?> + <?= $block->escapeHtml(__('This tab contains invalid data. Please resolve this before saving.')) ?> </span> </span> <span class="admin__page-nav-item-message-loader"> @@ -54,7 +55,7 @@ </span> </span> </a> - <div id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>_content" style="display:none;"<?= /* @escapeNotVerified */ $block->getUiId('tab', 'content', $_tab->getId()) ?>><?= /* @escapeNotVerified */ $block->getTabContent($_tab) ?></div> + <div id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>_content" style="display:none;"<?= /* @noEscape */ $block->getUiId('tab', 'content', $_tab->getId()) ?>><?= /* @noEscape */ $block->getTabContent($_tab) ?></div> </li> <?php endforeach; ?> </ul> @@ -63,11 +64,11 @@ <script> require(['jquery',"mage/backend/tabs"], function($){ $(function() { - $('#<?= /* @escapeNotVerified */ $block->getId() ?>').tabs({ - active: '<?= /* @escapeNotVerified */ $block->getActiveTabId() ?>', - destination: '#<?= /* @escapeNotVerified */ $block->getDestElementId() ?>', - shadowTabs: <?= /* @escapeNotVerified */ $block->getAllShadowTabs() ?>, - tabsBlockPrefix: '<?= /* @escapeNotVerified */ $block->getId() ?>_', + $('#<?= /* @noEscape */ $block->getId() ?>').tabs({ + active: '<?= /* @noEscape */ $block->getActiveTabId() ?>', + destination: '#<?= /* @noEscape */ $block->getDestElementId() ?>', + shadowTabs: <?= /* @noEscape */ $block->getAllShadowTabs() ?>, + tabsBlockPrefix: '<?= /* @noEscape */ $block->getId() ?>_', tabIdArgument: 'active_tab' }); }); diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml index c76f10da0f927..747dc577d2348 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml @@ -3,41 +3,38 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<!-- <?php if ($block->getTitle()): ?> - <h3><?= /* @escapeNotVerified */ $block->getTitle() ?></h3> +<!-- <?php if ($block->getTitle()) : ?> + <h3><?= $block->escapeHtml($block->getTitle()) ?></h3> <?php endif ?> --> -<?php if (!empty($tabs)): ?> -<div id="<?= /* @escapeNotVerified */ $block->getId() ?>"> +<?php if (!empty($tabs)) : ?> +<div id="<?= $block->escapeHtmlAttr($block->getId()) ?>"> <ul class="tabs-horiz"> -<?php foreach ($tabs as $_tab): ?> - <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> - <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> - <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> + <?php foreach ($tabs as $_tab) : ?> + <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> + <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> + <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> <li> <a href="<?= $block->escapeHtmlAttr($_tabHref) ?>" id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>" title="<?= $block->escapeHtmlAttr($block->getTabTitle($_tab)) ?>" class="<?= $block->escapeHtmlAttr($_tabClass) ?>" data-tab-type="<?= $block->escapeHtmlAttr($_tabType) ?>"> <span> - <span class="changed" title="<?= /* @escapeNotVerified */ __('The information in this tab has been changed.') ?>"></span> - <span class="error" title="<?= /* @escapeNotVerified */ __('This tab contains invalid data. Please resolve this before saving.') ?>"></span> - <span class="loader" title="<?= /* @escapeNotVerified */ __('Loading...') ?>"></span> - <?= /* @escapeNotVerified */ $block->getTabLabel($_tab) ?> + <span class="changed" title="<?= $block->escapeHtmlAttr(__('The information in this tab has been changed.')) ?>"></span> + <span class="error" title="<?= $block->escapeHtmlAttr(__('This tab contains invalid data. Please resolve this before saving.')) ?>"></span> + <span class="loader" title="<?= $block->escapeHtmlAttr(__('Loading...')) ?>"></span> + <?= $block->escapeHtml($block->getTabLabel($_tab)) ?> </span> </a> - <div id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>_content" style="display:none"><?= /* @escapeNotVerified */ $block->getTabContent($_tab) ?></div> + <div id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>_content" style="display:none"><?= /* @noEscape */ $block->getTabContent($_tab) ?></div> </li> -<?php endforeach; ?> + <?php endforeach; ?> </ul> </div> <script> require(["jquery","mage/backend/tabs"], function($){ $(function() { - $('#<?= /* @escapeNotVerified */ $block->getId() ?>').tabs({ - active: '<?= /* @escapeNotVerified */ $block->getActiveTabId() ?>', - destination: '#<?= /* @escapeNotVerified */ $block->getDestElementId() ?>', - shadowTabs: <?= /* @escapeNotVerified */ $block->getAllShadowTabs() ?> + $('#<?= /* @noEscape */ $block->getId() ?>').tabs({ + active: '<?= /* @noEscape */ $block->getActiveTabId() ?>', + destination: '#<?= /* @noEscape */ $block->getDestElementId() ?>', + shadowTabs: <?= /* @noEscape */ $block->getAllShadowTabs() ?> }); }); }); diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabsleft.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabsleft.phtml index 6af34ca502f4b..4f69191e79763 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabsleft.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabsleft.phtml @@ -3,21 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<dl id="dl-<?= /* @escapeNotVerified */ $id ?>" class="accordion"> -<?php foreach ($sections as $sectionId => $section): ?> - <dt id="dt-<?= /* @escapeNotVerified */ $id ?>-<?= /* @escapeNotVerified */ $sectionId ?>"> - <strong><?= /* @escapeNotVerified */ $section['title'] ?></strong> +<dl id="dl-<?= /* @noEscape */ $id ?>" class="accordion"> +<?php foreach ($sections as $sectionId => $section) : ?> + <dt id="dt-<?= /* @noEscape */ $id ?>-<?= /* @noEscape */ $sectionId ?>"> + <strong><?= $block->escapeHtml($section['title']) ?></strong> </dt> - <dd id="dd-<?= /* @escapeNotVerified */ $id ?>-<?= /* @escapeNotVerified */ $section['id'] ?>" class="section-menu <?= !empty($section['active']) ? 'open' : '' ?>"> + <dd id="dd-<?= /* @noEscape */ $id ?>-<?= /* @noEscape */ $section['id'] ?>" class="section-menu <?= !empty($section['active']) ? 'open' : '' ?>"> <ul> - <?php foreach ($section['children'] as $menuId => $menuItem): ?> - <li id="li-<?= /* @escapeNotVerified */ $id ?>-<?= /* @escapeNotVerified */ $sectionId ?>-<?= /* @escapeNotVerified */ $menuId ?>"> - <a href="#" title="<?= /* @escapeNotVerified */ $menuItem['title'] ?>"> - <span><?= /* @escapeNotVerified */ $menuItem['label'] ?></span> + <?php foreach ($section['children'] as $menuId => $menuItem) : ?> + <li id="li-<?= /* @noEscape */ $id ?>-<?= /* @noEscape */ $sectionId ?>-<?= /* @noEscape */ $menuId ?>"> + <a href="#" title="<?= $block->escapeHtmlAttr($menuItem['title']) ?>"> + <span><?= $block->escapeHtml($menuItem['label']) ?></span> </a> </li> <?php endforeach ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/view/container.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/view/container.phtml index f29e8fd8624c0..a0d56911ae274 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/view/container.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/view/container.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div data-mage-init='{"floatingHeader": {}}' class="page-actions"><?= $block->getButtonsHtml() ?></div> <?= $block->getViewHtml() ?> diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index f8fde7fa39d6f..64a6fd59cd4e2 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-cron": "100.3.*", @@ -24,5 +24,5 @@ "Magento\\Backup\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml index 304a60c60cad7..81aa49efd11e8 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml @@ -3,14 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <!-- TODO: refactor form styles and js --> <script type="text/x-magento-template" id="rollback-warning-template"> -<p><?= /* @escapeNotVerified */ __('You will lose any data created since the backup was made, including admin users, customers and orders.') ?></p> -<p><?= /* @escapeNotVerified */ __('Are you sure you want to continue?') ?></p> +<p><?= $block->escapeHtml(__('You will lose any data created since the backup was made, including admin users, customers and orders.')) ?></p> +<p><?= $block->escapeHtml(__('Are you sure you want to continue?')) ?></p> </script> <script type="text/x-magento-template" id="backup-options-template"> <div class="backup-messages" style="display: none;"> @@ -18,39 +15,39 @@ </div> <div class="messages"> <div class="message message-warning"> - <?= /* @escapeNotVerified */ __('This may take a few moments.') ?> - <?= /* @escapeNotVerified */ __('Be sure your store is in maintenance mode during backup.') ?></div> + <?= $block->escapeHtml(__('This may take a few moments.')) ?> + <?= $block->escapeHtml(__('Be sure your store is in maintenance mode during backup.')) ?></div> </div> <form action="" method="post" id="backup-form" class="form-inline"> <fieldset class="admin__fieldset form-list question"> <div class="admin__field field _required"> - <label for="backup_name" class="admin__field-label"><span><?= /* @escapeNotVerified */ __('Backup Name') ?></span></label> + <label for="backup_name" class="admin__field-label"><span><?= $block->escapeHtml(__('Backup Name')) ?></span></label> <div class="admin__field-control"> <input type="text" name="backup_name" id="backup_name" class="admin__control-text required-entry validate-alphanum-with-spaces validate-length maximum-length-50" maxlength="50" /> <div class="admin__field-note"> - <?= /* @escapeNotVerified */ __('Please use only letters (a-z or A-Z), numbers (0-9) or spaces in this field.') ?> + <?= $block->escapeHtml(__('Please use only letters (a-z or A-Z), numbers (0-9) or spaces in this field.')) ?> </div> </div> </div> <div class="admin__field field maintenance-checkbox-container"> - <label for="backup_maintenance_mode" class="admin__field-label"><span><?= /* @escapeNotVerified */ __('Maintenance mode') ?></span></label> + <label for="backup_maintenance_mode" class="admin__field-label"><span><?= $block->escapeHtml(__('Maintenance mode')) ?></span></label> <div class="admin__field-control"> <div class="admin__field-option"> <input class="admin__control-checkbox" type="checkbox" name="maintenance_mode" value="1" id="backup_maintenance_mode"/> - <label class="admin__field-label" for="backup_maintenance_mode"><?= /* @escapeNotVerified */ __('Please put your store into maintenance mode during backup.') ?></label> + <label class="admin__field-label" for="backup_maintenance_mode"><?= $block->escapeHtml(__('Please put your store into maintenance mode during backup.')) ?></label> </div> </div> </div> <div class="admin__field field maintenance-checkbox-container" id="exclude-media-checkbox-container" style="display: none;"> - <label for="exclude_media" class="admin__field-label"><span><?= /* @escapeNotVerified */ __('Exclude') ?></span></label> + <label for="exclude_media" class="admin__field-label"><span><?= $block->escapeHtml(__('Exclude')) ?></span></label> <div class="admin__field-control"> <div class="admin__field-option"> <input class="admin__control-checkbox" type="checkbox" name="exclude_media" value="1" id="exclude_media"/> - <label class="admin__field-label" for="exclude_media"><?= /* @escapeNotVerified */ __('Exclude media folder from backup') ?></label> + <label class="admin__field-label" for="exclude_media"><?= $block->escapeHtml(__('Exclude media folder from backup')) ?></label> </div> </div> </div> @@ -64,59 +61,59 @@ </div> <div class="messages"> <div class="message message-warning"> - <?= /* @escapeNotVerified */ __('Please enter the password to confirm rollback.') ?><br> - <?= /* @escapeNotVerified */ __('This action cannot be undone.') ?> - <p><?= /* @escapeNotVerified */ __('Are you sure you want to continue?') ?></p> + <?= $block->escapeHtml(__('Please enter the password to confirm rollback.')) ?><br> + <?= $block->escapeHtml(__('This action cannot be undone.')) ?> + <p><?= $block->escapeHtml(__('Are you sure you want to continue?')) ?></p> </div> </div> <form action="" method="post" id="rollback-form" class="form-inline"> <fieldset class="admin__fieldset password-box-container"> <div class="admin__field field _required"> - <label for="password" class="admin__field-label"><span><?= /* @escapeNotVerified */ __('User Password') ?></span></label> + <label for="password" class="admin__field-label"><span><?= $block->escapeHtml(__('User Password')) ?></span></label> <div class="admin__field-control"><input type="password" name="password" id="password" class="admin__control-text required-entry" autocomplete="new-password"></div> </div> <div class="admin__field field maintenance-checkbox-container"> - <label for="rollback_maintenance_mode" class="admin__field-label"><span><?= /* @escapeNotVerified */ __('Maintenance mode') ?></span></label> + <label for="rollback_maintenance_mode" class="admin__field-label"><span><?= $block->escapeHtml(__('Maintenance mode')) ?></span></label> <div class="admin__field-control"> <div class="admin__field-option"> - <input class="admin__control-checkbox" type="checkbox" name="maintenance_mode" value="1" id="rollback_maintenance_mode"/> - <label class="admin__field-label" for="rollback_maintenance_mode"><?= /* @escapeNotVerified */ __('Please put your store into maintenance mode during rollback processing.') ?></label> + <input class="admin__control-checkbox" type="checkbox" name="maintenance_mode" value="1" id="rollback_maintenance_mode"/> + <label class="admin__field-label" for="rollback_maintenance_mode"><?= $block->escapeHtml(__('Please put your store into maintenance mode during rollback processing.')) ?></label> </div> </div> </div> <div class="admin__field field maintenance-checkbox-container" id="use-ftp-checkbox-row" style="display: none;"> <label for="use_ftp" class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('FTP') ?></span> + <span><?= $block->escapeHtml(__('FTP')) ?></span> </label> <div class="admin__field-control"> <div class="admin__field-option"> <input class="admin__control-checkbox" type="checkbox" name="use_ftp" value="1" id="use_ftp" onclick="backup.toggleFtpCredentialsForm(event)"/> - <label class="admin__field-label" for="use_ftp"><?= /* @escapeNotVerified */ __('Use FTP Connection') ?></label> + <label class="admin__field-label" for="use_ftp"><?= $block->escapeHtml(__('Use FTP Connection')) ?></label> </div> </div> </div> </fieldset> <div class="entry-edit" id="ftp-credentials-container" style="display: none;"> <fieldset class="admin__fieldset"> - <legend class="admin__legend legend"><span><?= /* @escapeNotVerified */ __('FTP credentials') ?></span></legend><br /> + <legend class="admin__legend legend"><span><?= $block->escapeHtml(__('FTP credentials')) ?></span></legend><br /> <div class="admin__field field _required"> - <label class="admin__field-label" for="ftp_host"><span><?= /* @escapeNotVerified */ __('FTP Host') ?></span></label> + <label class="admin__field-label" for="ftp_host"><span><?= $block->escapeHtml(__('FTP Host')) ?></span></label> <div class="admin__field-control"> <input type="text" class="admin__control-text" name="ftp_host" id="ftp_host"> </div> </div> <div class="admin__field field _required"> - <label class="admin__field-label" for="ftp_user"><span><?= /* @escapeNotVerified */ __('FTP Login') ?></span></label> + <label class="admin__field-label" for="ftp_user"><span><?= $block->escapeHtml(__('FTP Login')) ?></span></label> <div class="admin__field-control"> <input type="text" class="admin__control-text" name="ftp_user" id="ftp_user"> </div> </div> <div class="admin__field field _required"> <label class="admin__field-label" for="ftp_pass"> - <span><?= /* @escapeNotVerified */ __('FTP Password') ?></span> + <span><?= $block->escapeHtml(__('FTP Password')) ?></span> </label> <div class="admin__field-control"> <input type="password" class="admin__control-text" name="ftp_pass" id="ftp_pass" autocomplete="new-password"> @@ -124,7 +121,7 @@ </div> <div class="admin__field field"> <label class="admin__field-label" for="ftp_path"> - <span><?= /* @escapeNotVerified */ __('Magento root directory') ?></span> + <span><?= $block->escapeHtml(__('Magento root directory')) ?></span> </label> <div class="admin__field-control"> <input type="text" class="admin__control-text" name="ftp_path" id="ftp_path"> diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/left.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/left.phtml index 1196f7e443820..cecff38611bfd 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/left.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/left.phtml @@ -4,4 +4,4 @@ * See COPYING.txt for license details. */ ?> -<h3><?= /* @escapeNotVerified */ __('Create Backup') ?></h3> +<h3><?= $block->escapeHtml(__('Create Backup')) ?></h3> diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml index 9ac9dd496e066..4982953e314d3 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/list.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml('grid') ?> <?= $block->getGridHtml() ?> diff --git a/app/code/Magento/Braintree/Block/Paypal/Button.php b/app/code/Magento/Braintree/Block/Paypal/Button.php index efd9e473699c3..fe829cf9f1fdd 100644 --- a/app/code/Magento/Braintree/Block/Paypal/Button.php +++ b/app/code/Magento/Braintree/Block/Paypal/Button.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Braintree\Block\Paypal; use Magento\Braintree\Gateway\Config\PayPal\Config; @@ -49,8 +51,6 @@ class Button extends Template implements ShortcutInterface private $payment; /** - * Constructor - * * @param Context $context * @param ResolverInterface $localeResolver * @param Session $checkoutSession @@ -98,6 +98,8 @@ public function getAlias() } /** + * Returns container id. + * * @return string */ public function getContainerId() @@ -106,6 +108,8 @@ public function getContainerId() } /** + * Returns locale. + * * @return string */ public function getLocale() @@ -114,6 +118,8 @@ public function getLocale() } /** + * Returns currency. + * * @return string */ public function getCurrency() @@ -122,6 +128,8 @@ public function getCurrency() } /** + * Returns amount. + * * @return float */ public function getAmount() @@ -130,6 +138,8 @@ public function getAmount() } /** + * Returns if is active. + * * @return bool */ public function isActive() @@ -139,6 +149,8 @@ public function isActive() } /** + * Returns merchant name. + * * @return string */ public function getMerchantName() @@ -147,6 +159,8 @@ public function getMerchantName() } /** + * Returns client token. + * * @return string|null */ public function getClientToken() @@ -155,10 +169,22 @@ public function getClientToken() } /** + * Returns action success. + * * @return string */ public function getActionSuccess() { return $this->getUrl(ConfigProvider::CODE . '/paypal/review', ['_secure' => true]); } + + /** + * Gets environment value. + * + * @return string + */ + public function getEnvironment(): string + { + return $this->configProvider->getConfig()['payment'][ConfigProvider::CODE]['environment']; + } } diff --git a/app/code/Magento/Braintree/Gateway/Config/Config.php b/app/code/Magento/Braintree/Gateway/Config/Config.php index 2089a9646ae94..905b802061aa6 100644 --- a/app/code/Magento/Braintree/Gateway/Config/Config.php +++ b/app/code/Magento/Braintree/Gateway/Config/Config.php @@ -30,7 +30,6 @@ class Config extends \Magento\Payment\Gateway\Config\Config const KEY_VERIFY_SPECIFIC = 'verify_specific_countries'; const VALUE_3DSECURE_ALL = 0; const CODE_3DSECURE = 'three_d_secure'; - const KEY_KOUNT_MERCHANT_ID = 'kount_id'; const FRAUD_PROTECTION = 'fraudprotection'; /** @@ -173,6 +172,7 @@ public function get3DSecureSpecificCountries($storeId = null) /** * Gets value of configured environment. + * * Possible values: production or sandbox. * * @param int|null $storeId @@ -183,17 +183,6 @@ public function getEnvironment($storeId = null) return $this->getValue(Config::KEY_ENVIRONMENT, $storeId); } - /** - * Gets Kount merchant ID. - * - * @param int|null $storeId - * @return string - */ - public function getKountMerchantId($storeId = null) - { - return $this->getValue(Config::KEY_KOUNT_MERCHANT_ID, $storeId); - } - /** * Gets merchant ID. * @@ -217,6 +206,8 @@ public function getMerchantAccountId($storeId = null) } /** + * Returns SDK url. + * * @return string */ public function getSdkUrl() @@ -224,6 +215,16 @@ public function getSdkUrl() return $this->getValue(Config::KEY_SDK_URL); } + /** + * Gets Hosted Fields SDK Url + * + * @return string + */ + public function getHostedFieldsSdkUrl(): string + { + return $this->getValue('hosted_fields_sdk_url'); + } + /** * Checks if fraud protection is enabled. * diff --git a/app/code/Magento/Braintree/Gateway/Request/VaultThreeDSecureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VaultThreeDSecureDataBuilder.php new file mode 100644 index 0000000000000..5441067b9d813 --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Request/VaultThreeDSecureDataBuilder.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Gateway\Request; + +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Request\BuilderInterface; + +/** + * Since we can't validate 3Dsecure for sequence multishipping orders based on vault tokens, + * we skip 3D secure verification for vault transactions. + * For common vault transaction original 3d secure verification builder is called. + */ +class VaultThreeDSecureDataBuilder implements BuilderInterface +{ + /** + * @var ThreeDSecureDataBuilder + */ + private $threeDSecureDataBuilder; + + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * Constructor + * + * @param ThreeDSecureDataBuilder $threeDSecureDataBuilder + * @param SubjectReader $subjectReader + */ + public function __construct( + ThreeDSecureDataBuilder $threeDSecureDataBuilder, + SubjectReader $subjectReader + ) { + $this->threeDSecureDataBuilder = $threeDSecureDataBuilder; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject) + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + if ($payment->getAdditionalInformation('is_multishipping')) { + return []; + } + + return $this->threeDSecureDataBuilder->build($buildSubject); + } +} diff --git a/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php index 167fcb1569cbf..58ce33305da85 100644 --- a/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php +++ b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php @@ -38,6 +38,14 @@ public function getErrorCodes($response): array $result[] = $error->code; } + if (isset($response->transaction) && $response->transaction->status === 'gateway_rejected') { + $result[] = $response->transaction->gatewayRejectionReason; + } + + if (isset($response->transaction) && $response->transaction->status === 'processor_declined') { + $result[] = $response->transaction->processorResponseCode; + } + return $result; } } diff --git a/app/code/Magento/Braintree/Model/LocaleResolver.php b/app/code/Magento/Braintree/Model/LocaleResolver.php index cebd90dffc70e..418149b2978ae 100644 --- a/app/code/Magento/Braintree/Model/LocaleResolver.php +++ b/app/code/Magento/Braintree/Model/LocaleResolver.php @@ -8,6 +8,9 @@ use Magento\Framework\Locale\ResolverInterface; use Magento\Braintree\Gateway\Config\PayPal\Config; +/** + * Resolves locale for PayPal Express. + */ class LocaleResolver implements ResolverInterface { /** @@ -20,6 +23,17 @@ class LocaleResolver implements ResolverInterface */ private $config; + /** + * Mapping Magento locales on PayPal locales. + * + * @var array + */ + private $localeMap = [ + 'zh_Hans_CN' => 'zh_CN', + 'zh_Hant_HK' => 'zh_HK', + 'zh_Hant_TW' => 'zh_TW' + ]; + /** * @param ResolverInterface $resolver * @param Config $config @@ -66,13 +80,14 @@ public function setLocale($locale = null) * Gets store's locale or the `en_US` locale if store's locale does not supported by PayPal. * * @return string + * @see https://braintree.github.io/braintree-web/current/PayPalCheckout.html#createPayment */ public function getLocale() { - $locale = $this->resolver->getLocale(); + $locale = $this->localeMap[$this->resolver->getLocale()] ?? $this->resolver->getLocale(); $allowedLocales = $this->config->getValue('supported_locales'); - return strpos($allowedLocales, $locale) !== false ? $locale : 'en_US'; + return strpos($allowedLocales, (string) $locale) !== false ? $locale : 'en_US'; } /** diff --git a/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php index a6c1b088400a7..a95d7a922f9bd 100644 --- a/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php +++ b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php @@ -8,6 +8,7 @@ namespace Magento\Braintree\Model\Multishipping; use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Gateway\Config\Config; use Magento\Braintree\Model\Ui\ConfigProvider; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PaypalConfigProvider; @@ -118,6 +119,10 @@ private function setVaultPayment(OrderPaymentInterface $orderPayment, PaymentTok PaymentTokenInterface::CUSTOMER_ID, $customerId ); + $orderPayment->setAdditionalInformation( + 'is_multishipping', + 1 + ); } /** diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php index ae2b1b1423640..197b398380f74 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php @@ -173,14 +173,14 @@ private function updateBillingAddress(Quote $quote, array $details) */ private function updateAddressData(Address $address, array $addressData) { - $extendedAddress = isset($addressData['extendedAddress']) - ? $addressData['extendedAddress'] + $extendedAddress = isset($addressData['line2']) + ? $addressData['line2'] : null; - $address->setStreet([$addressData['streetAddress'], $extendedAddress]); - $address->setCity($addressData['locality']); - $address->setRegionCode($addressData['region']); - $address->setCountryId($addressData['countryCodeAlpha2']); + $address->setStreet([$addressData['line1'], $extendedAddress]); + $address->setCity($addressData['city']); + $address->setRegionCode($addressData['state']); + $address->setCountryId($addressData['countryCode']); $address->setPostcode($addressData['postalCode']); // PayPal's address supposes not saving against customer account diff --git a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php index 928769498a035..ab23037b4e98e 100644 --- a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php @@ -13,6 +13,8 @@ /** * Class ConfigProvider + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class ConfigProvider implements ConfigProviderInterface { @@ -72,11 +74,11 @@ public function getConfig() 'clientToken' => $this->getClientToken(), 'ccTypesMapper' => $this->config->getCcTypesMapper(), 'sdkUrl' => $this->config->getSdkUrl(), + 'hostedFieldsSdkUrl' => $this->config->getHostedFieldsSdkUrl(), 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig($storeId), 'availableCardTypes' => $this->config->getAvailableCardTypes($storeId), 'useCvv' => $this->config->isCvvEnabled($storeId), 'environment' => $this->config->getEnvironment($storeId), - 'kountMerchantId' => $this->config->getKountMerchantId($storeId), 'hasFraudProtection' => $this->config->hasFraudProtection($storeId), 'merchantId' => $this->config->getMerchantId($storeId), 'ccVaultCode' => self::CC_VAULT_CODE, @@ -92,6 +94,7 @@ public function getConfig() /** * Generate a new client token if necessary + * * @return string */ public function getClientToken() diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml index 3f8bdaa4cd6bd..d530f8e9f68b0 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml @@ -38,18 +38,4 @@ <waitForPageLoad stepKey="waitForSaveUser" time="10"/> <see userInput="You saved the user." stepKey="seeSuccessMessage" /> </actionGroup> - - <!--Delete User--> - <actionGroup name="AdminDeleteNewUserActionGroup"> - - <click stepKey="clickOnUser" selector="{{AdminDeleteUserSection.theUser}}"/> - <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> - <scrollToTopOfPage stepKey="scrollToTop"/> - <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> - <waitForPageLoad stepKey="waitForDeletePopupOpen" time="5"/> - <click stepKey="clickToConfirm" selector="{{AdminDeleteUserSection.confirm}}"/> - <waitForPageLoad stepKey="waitForPageLoad" time="10"/> - <see userInput="You deleted the user." stepKey="seeSuccessMessage" /> - </actionGroup> - </actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml index cbb065704fbc1..f2c050006d010 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - + <!-- Skip by MQE-1576 --> <actionGroup name="ConfigureBraintree"> <!-- GoTo ConfigureBraintree fields --> <click stepKey="clickOnSTORES" selector="{{AdminMenuSection.stores}}"/> @@ -50,4 +50,4 @@ <magentoCLI stepKey="disableBrainTree" command="config:set payment/braintree/active 0"/> <magentoCLI stepKey="disableBrainTreePaypal" command="config:set payment/braintree_paypal/active 0"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml index f00e3fa286b08..f95ba2eb590dc 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml @@ -6,8 +6,7 @@ */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SampleBraintreeConfig" type="braintree_config_state"> <requiredEntity type="title">SampleTitle</requiredEntity> <requiredEntity type="payment_action">SamplePaymentAction</requiredEntity> @@ -54,13 +53,13 @@ <data key="value">sandbox</data> </entity> <entity name="MerchantId" type="merchant_id"> - <data key="value">d4pdjhxgjfrsmzbf</data> + <data key="value">MERCH_ID</data> </entity> <entity name="PublicKey" type="public_key"> - <data key="value">m7q4wmh43xrgyrst</data> + <data key="value">PUBLIC_KEY</data> </entity> <entity name="PrivateKey" type="private_key"> - <data key="value">67de364080b1b4e2492d7a3de413a572</data> + <data key="value">PRIVATE_KEY</data> </entity> <!-- default configuration used to restore Magento config --> @@ -138,14 +137,17 @@ <data key="year">20</data> <data key="cvv">113</data> </entity> + <entity name="StoredPaymentMethods"> + <data key="cardNumberEnding">5100</data> + <data key="cardExpire">12/2020</data> + </entity> <entity name="BraintreeConfigurationData" type="data"> <data key="title">Credit Card (Braintree)</data> - <data key="merchantID">d4pdjhxgjfrsmzbf</data> - <data key="publicKey">m7q4wmh43xrgyrst</data> - <data key="privateKey">67de364080b1b4e2492d7a3de413a572</data> - <data key="merchantAccountID">Magneto</data> + <data key="merchantID">MERCH_ID</data> + <data key="publicKey">PUBLIC_KEY</data> + <data key="privateKey">PRIVATE_KEY</data> + <data key="merchantAccountID">MERCH_ACCOUNT_ID</data> <data key="titleForPayPalThroughBraintree">PayPal (Braintree)</data> </entity> - </entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml index a781841e0a77b..9a1110bfda29a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MAGETWO-93767"/> <group value="braintree"/> + <skip> + <issueId value="MQE-1576"/> + </skip> </annotations> <before> @@ -49,6 +52,7 @@ <waitForPageLoad stepKey="waitForPageLoad"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForPageLoad1"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!--Proceed to checkout--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> @@ -74,6 +78,7 @@ <waitForPageLoad stepKey="waitForPageLoad6"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart1"/> <waitForPageLoad stepKey="waitForPageLoad7"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> <!--Proceed to checkout--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup1"/> <click selector="{{CheckoutPaymentSection.addressAction('New Address')}}" stepKey="clickOnNewAddress"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml index 244052371e702..2594d245f9ff0 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-93677"/> <group value="braintree"/> + <skip> + <issueId value="MQE-1576"/> + </skip> </annotations> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml index cf51f29db79f9..8f0ce4918b978 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-94472"/> <group value="braintree"/> + <skip> + <issueId value="MQE-1576"/> + </skip> </annotations> <before> diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php new file mode 100644 index 0000000000000..cddb4852da0e3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Gateway\Validator; + +use Braintree\Result\Error; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; +use PHPUnit\Framework\TestCase; + +/** + * Class ErrorCodeProviderTest + */ +class ErrorCodeProviderTest extends TestCase +{ + /** + * @var ErrorCodeProvider + */ + private $model; + + /** + * Checks a extracting error codes from response. + * + * @param array $errors + * @param array $transaction + * @param array $expectedResult + * @return void + * @dataProvider getErrorCodeDataProvider + */ + public function testGetErrorCodes(array $errors, array $transaction, array $expectedResult): void + { + $response = new Error( + [ + 'errors' => ['errors' => $errors], + 'transaction' => $transaction, + ] + ); + $this->model = new ErrorCodeProvider(); + $actual = $this->model->getErrorCodes($response); + + $this->assertSame($expectedResult, $actual); + } + + /** + * Gets list of errors variations. + * + * @return array + */ + public function getErrorCodeDataProvider(): array + { + return [ + [ + 'errors' => [ + ['code' => 91734], + ['code' => 91504] + ], + 'transaction' => [ + 'status' => 'success', + ], + 'expectedResult' => ['91734', '91504'] + ], + [ + 'errors' => [], + 'transaction' => [ + 'status' => 'processor_declined', + 'processorResponseCode' => '1000' + ], + 'expectedResult' => ['1000'] + ], + [ + 'errors' => [], + 'transaction' => [ + 'status' => 'processor_declined', + 'processorResponseCode' => '1000' + ], + 'expectedResult' => ['1000'] + ], + [ + 'errors' => [ + ['code' => 91734], + ['code' => 91504] + ], + 'transaction' => [ + 'status' => 'processor_declined', + 'processorResponseCode' => '1000' + ], + 'expectedResult' => ['91734', '91504', '1000'] + ], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php index 4741a3ea38c6f..d966e4e3f10ec 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php @@ -14,6 +14,9 @@ use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; use PHPUnit_Framework_MockObject_MockObject as MockObject; +/** + * Class GeneralResponseValidatorTest + */ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase { /** @@ -61,11 +64,13 @@ public function testValidate(array $validationSubject, bool $isValid, $messages, $result = new Result($isValid, $messages); $this->resultInterfaceFactory->method('create') - ->with([ - 'isValid' => $isValid, - 'failsDescription' => $messages, - 'errorCodes' => $errorCodes - ]) + ->with( + [ + 'isValid' => $isValid, + 'failsDescription' => $messages, + 'errorCodes' => $errorCodes + ] + ) ->willReturn($result); $actual = $this->responseValidator->validate($validationSubject); @@ -82,9 +87,11 @@ public function dataProviderTestValidate() { $successTransaction = new \stdClass(); $successTransaction->success = true; + $successTransaction->status = 'authorized'; $failureTransaction = new \stdClass(); $failureTransaction->success = false; + $failureTransaction->status = 'declined'; $failureTransaction->message = 'Transaction was failed.'; $errors = [ @@ -93,10 +100,10 @@ public function dataProviderTestValidate() 'code' => 81804, 'attribute' => 'base', 'message' => 'Cannot process transaction.' - ] + ], ] ]; - $errorTransaction = new Error(['errors' => $errors]); + $errorTransaction = new Error(['errors' => $errors, 'transaction' => ['status' => 'declined']]); return [ [ diff --git a/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php index b6ef534c55c29..f80b630766407 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php @@ -98,19 +98,35 @@ public function testSetLocale() /** * Test getLocale method * - * @return void + * @param string $locale + * @param string $expectedLocale + * @dataProvider getLocaleDataProvider */ - public function testGetLocale() + public function testGetLocale(string $locale, string $expectedLocale) { - $locale = 'en_TEST'; - $allowedLocales = 'en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL'; - $this->resolverMock->expects($this->once())->method('getLocale')->willReturn($locale); - $this->configMock->expects($this->once())->method('getValue')->with('supported_locales') + $allowedLocales = 'en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,zh_CN,zh_TW,nl_NL'; + $this->resolverMock->method('getLocale') + ->willReturn($locale); + $this->configMock->method('getValue') + ->with('supported_locales') ->willReturn($allowedLocales); - - $expected = 'en_US'; $actual = $this->localeResolver->getLocale(); - self::assertEquals($expected, $actual); + + self::assertEquals($expectedLocale, $actual); + } + + /** + * @return array + */ + public function getLocaleDataProvider(): array + { + return [ + ['locale' => 'zh_Hans_CN', 'expectedLocale' => 'zh_CN'], + ['locale' => 'zh_Hant_HK', 'expectedLocale' => 'zh_HK'], + ['locale' => 'zh_Hant_TW', 'expectedLocale' => 'zh_TW'], + ['locale' => 'fr_FR', 'expectedLocale' => 'fr_FR'], + ['locale' => 'unknown', 'expectedLocale' => 'en_US'], + ]; } /** diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php index c2678d1c78437..ec716732b114e 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php @@ -159,21 +159,21 @@ private function getDetails(): array 'phone' => '312-123-4567', 'countryCode' => 'US', 'shippingAddress' => [ - 'streetAddress' => '123 Division Street', - 'extendedAddress' => 'Apt. #1', - 'locality' => 'Chicago', - 'region' => 'IL', + 'line1' => '123 Division Street', + 'line2' => 'Apt. #1', + 'city' => 'Chicago', + 'state' => 'IL', 'postalCode' => '60618', - 'countryCodeAlpha2' => 'US', + 'countryCode' => 'US', 'recipientName' => 'Jane Smith', ], 'billingAddress' => [ - 'streetAddress' => '123 Billing Street', - 'extendedAddress' => 'Apt. #1', - 'locality' => 'Chicago', - 'region' => 'IL', + 'line1' => '123 Billing Street', + 'line2' => 'Apt. #1', + 'city' => 'Chicago', + 'state' => 'IL', 'postalCode' => '60618', - 'countryCodeAlpha2' => 'US', + 'countryCode' => 'US', ], ]; } @@ -206,13 +206,13 @@ private function updateShippingAddressStep(array $details): void private function updateAddressDataStep(MockObject $address, array $addressData): void { $address->method('setStreet') - ->with([$addressData['streetAddress'], $addressData['extendedAddress']]); + ->with([$addressData['line1'], $addressData['line2']]); $address->method('setCity') - ->with($addressData['locality']); + ->with($addressData['city']); $address->method('setRegionCode') - ->with($addressData['region']); + ->with($addressData['state']); $address->method('setCountryId') - ->with($addressData['countryCodeAlpha2']); + ->with($addressData['countryCode']); $address->method('setPostcode') ->with($addressData['postalCode']); } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php index 24bc4eae960be..55bc2cb195d6e 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Braintree\Test\Unit\Model\Ui; use Magento\Braintree\Gateway\Config\Config; @@ -124,6 +126,7 @@ public function getConfigDataProvider() 'isActive' => true, 'getCcTypesMapper' => ['visa' => 'VI', 'american-express'=> 'AE'], 'getSdkUrl' => self::SDK_URL, + 'getHostedFieldsSdkUrl' => 'https://sdk.com/test.js', 'getCountrySpecificCardTypeConfig' => [ 'GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB'] @@ -134,7 +137,6 @@ public function getConfigDataProvider() 'getThresholdAmount' => 20, 'get3DSecureSpecificCountries' => ['GB', 'US', 'CA'], 'getEnvironment' => 'test-environment', - 'getKountMerchantId' => 'test-kount-merchant-id', 'getMerchantId' => 'test-merchant-id', 'hasFraudProtection' => true, ], @@ -145,6 +147,7 @@ public function getConfigDataProvider() 'clientToken' => self::CLIENT_TOKEN, 'ccTypesMapper' => ['visa' => 'VI', 'american-express' => 'AE'], 'sdkUrl' => self::SDK_URL, + 'hostedFieldsSdkUrl' => 'https://sdk.com/test.js', 'countrySpecificCardTypes' =>[ 'GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB'] @@ -152,7 +155,6 @@ public function getConfigDataProvider() 'availableCardTypes' => ['AE', 'VI', 'MC', 'DI', 'JCB'], 'useCvv' => true, 'environment' => 'test-environment', - 'kountMerchantId' => 'test-kount-merchant-id', 'merchantId' => 'test-merchant-id', 'hasFraudProtection' => true, 'ccVaultCode' => ConfigProvider::CC_VAULT_CODE diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 10bfb19865ac5..c3aadf13b3563 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "braintree/braintree_php": "3.35.0", "magento/framework": "102.0.*", "magento/magento-composer-installer": "*", @@ -41,5 +41,5 @@ "Magento\\Braintree\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index 5215dbc00b7ef..bd4346e095c6d 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -41,7 +41,7 @@ </requires> </field> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/braintree.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/braintree.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="braintree_required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="5"> @@ -95,14 +95,6 @@ <comment>Be sure to Enable Advanced Fraud Protection in Your Braintree Account in Settings/Processing Section</comment> <config_path>payment/braintree/fraudprotection</config_path> </field> - <field id="kount_id" translate="label comment" sortOrder="35" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Kount Merchant ID</label> - <comment><![CDATA[Used for direct fraud tool integration. Make sure you also contact <a href="mailto:accounts@braintreepayments.com">accounts@braintreepayments.com</a> to setup your Kount account.]]></comment> - <depends> - <field id="fraudprotection">1</field> - </depends> - <config_path>payment/braintree/kount_id</config_path> - </field> <field id="debug" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml index 81da0a252e567..7155264b4e6ad 100644 --- a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml +++ b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml @@ -20,6 +20,7 @@ <message code="81716" translate="true">Credit card number must be 12-19 digits.</message> <message code="81723" translate="true">Cardholder name is too long.</message> <message code="81736" translate="true">CVV verification failed.</message> + <message code="cvv" translate="true">CVV verification failed.</message> <message code="81737" translate="true">Postal code verification failed.</message> <message code="81750" translate="true">Credit card number is prohibited.</message> <message code="81801" translate="true">Addresses must have at least one field filled in.</message> diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index fe4cfab9c0e30..522d32302168e 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -34,7 +34,8 @@ <order_status>processing</order_status> <environment>sandbox</environment> <allowspecific>0</allowspecific> - <sdk_url><![CDATA[https://js.braintreegateway.com/js/braintree-2.32.0.min.js]]></sdk_url> + <sdk_url><![CDATA[https://js.braintreegateway.com/web/3.44.1/js/client.min.js]]></sdk_url> + <hosted_fields_sdk_url><![CDATA[https://js.braintreegateway.com/web/3.44.1/js/hosted-fields.min.js]]></hosted_fields_sdk_url> <public_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> <private_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> <masked_fields>cvv,number</masked_fields> diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index b81513caf17a2..6f8b7d1d6c368 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -288,7 +288,7 @@ <item name="payment" xsi:type="string">Magento\Braintree\Gateway\Request\PaymentDataBuilder</item> <item name="channel" xsi:type="string">Magento\Braintree\Gateway\Request\ChannelDataBuilder</item> <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> - <item name="3dsecure" xsi:type="string">Magento\Braintree\Gateway\Request\ThreeDSecureDataBuilder</item> + <item name="3dsecure" xsi:type="string">Magento\Braintree\Gateway\Request\VaultThreeDSecureDataBuilder</item> <item name="device_data" xsi:type="string">Magento\Braintree\Gateway\Request\KountPaymentDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> @@ -614,7 +614,6 @@ <item name="payment/braintree/merchant_id" xsi:type="string">1</item> <item name="payment/braintree/private_key" xsi:type="string">1</item> <item name="payment/braintree/merchant_account_id" xsi:type="string">1</item> - <item name="payment/braintree/kount_id" xsi:type="string">1</item> <item name="payment/braintree_paypal/merchant_name_override" xsi:type="string">1</item> <item name="payment/braintree/descriptor_phone" xsi:type="string">1</item> <item name="payment/braintree/descriptor_url" xsi:type="string">1</item> diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml index 4c15fffa8189f..0d2599ff45f25 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var Magento\Braintree\Block\Form $block */ $code = $block->escapeHtml($block->getMethodCode()); @@ -22,9 +20,9 @@ $ccType = $block->getInfoData('cc_type'); <div class="admin__field-control control"> <select id="<?= /* @noEscape */ $code ?>_cc_type" name="payment[cc_type]" class="required-entry select admin__control-select validate-cc-type-select"> - <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName): ?> + <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName) : ?> <option value="<?= $block->escapeHtml($typeCode) ?>" - <?php if($typeCode == $ccType): ?> selected="selected"<?php endif; ?>> + <?php if ($typeCode == $ccType) : ?> selected="selected"<?php endif; ?>> <?= $block->escapeHtml($typeName) ?> </option> <?php endforeach; ?> @@ -61,7 +59,7 @@ $ccType = $block->getInfoData('cc_type'); </div> </div> </div> - <?php if($block->hasVerification()): ?> + <?php if ($block->hasVerification()) : ?> <div class="admin__field _required"> <label class="label admin__field-label"> <span><?= $block->escapeHtml(__('Card Verification Number')) ?></span> @@ -77,7 +75,7 @@ $ccType = $block->getInfoData('cc_type'); </div> <?php endif; ?> - <?php if($block->isVaultEnabled()): ?> + <?php if ($block->isVaultEnabled()) : ?> <div class="field-tooltip-content"> <input type="checkbox" id="<?= /* @noEscape */ $code ?>_vault" diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml index 22a0f8d8c987d..132f053d3bf89 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml @@ -1,10 +1,10 @@ <?php -use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; /** @var \Magento\Framework\View\Element\Template $block */ $details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml index 77d08dbc5273c..49ce2cc7c065c 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml @@ -1,10 +1,10 @@ <?php -use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; /** @var \Magento\Framework\View\Element\Template $block */ $details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js index ab01565d7f1e5..845ed03725bfb 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js @@ -22,9 +22,16 @@ define([ container: 'payment_form_braintree', active: false, scriptLoaded: false, - braintree: null, + braintreeClient: null, + braintreeHostedFields: null, + hostedFieldsInstance: null, selectedCardType: null, - checkout: null, + selectorsMapper: { + 'expirationMonth': 'expirationMonth', + 'expirationYear': 'expirationYear', + 'number': 'cc_number', + 'cvv': 'cc_cid' + }, imports: { onActiveChange: 'active' } @@ -108,9 +115,10 @@ define([ state = self.scriptLoaded; $('body').trigger('processStart'); - require([this.sdkUrl], function (braintree) { + require([this.sdkUrl, this.hostedFieldsSdkUrl], function (client, hostedFields) { state(true); - self.braintree = braintree; + self.braintreeClient = client; + self.braintreeHostedFields = hostedFields; self.initBraintree(); $('body').trigger('processStop'); }); @@ -146,46 +154,26 @@ define([ _initBraintree: function () { var self = this; - this.disableEventListeners(); - - if (self.checkout) { - self.checkout.teardown(function () { - self.checkout = null; - }); - } - - self.braintree.setup(self.clientToken, 'custom', { - id: self.selector, - hostedFields: self.getHostedFields(), - - /** - * Triggered when sdk was loaded - */ - onReady: function (checkout) { - self.checkout = checkout; - $('body').trigger('processStop'); + self.disableEventListeners(); + + self.braintreeClient.create({ + authorization: self.clientToken + }) + .then(function (clientInstance) { + return self.braintreeHostedFields.create({ + client: clientInstance, + fields: self.getHostedFields() + }); + }) + .then(function (hostedFieldsInstance) { + self.hostedFieldsInstance = hostedFieldsInstance; self.enableEventListeners(); - }, - - /** - * Callback for success response - * @param {Object} response - */ - onPaymentMethodReceived: function (response) { - if (self.validateCardType()) { - self.setPaymentDetails(response.nonce); - self.placeOrder(); - } - }, - - /** - * Error callback - * @param {Object} response - */ - onError: function (response) { - self.error(response.message); - } - }); + self.fieldEventHandler(hostedFieldsInstance); + $('body').trigger('processStop'); + }) + .catch(function () { + self.error($t('Braintree can\'t be initialized.')); + }); }, /** @@ -205,14 +193,6 @@ define([ expirationYear: { selector: self.getSelector('cc_exp_year'), placeholder: $t('YY') - }, - - /** - * Triggered when hosted field is changed - * @param {Object} event - */ - onFieldEvent: function (event) { - return self.fieldEventHandler(event); } }; @@ -227,36 +207,49 @@ define([ /** * Function to handle hosted fields events - * @param {Object} event - * @returns {Boolean} + * @param {Object} hostedFieldsInstance */ - fieldEventHandler: function (event) { + fieldEventHandler: function (hostedFieldsInstance) { var self = this, $cardType = $('#' + self.container).find('.icon-type'); - if (event.isEmpty === false) { - self.validateCardType(); - } + hostedFieldsInstance.on('empty', function (event) { + if (event.emittedBy === 'number') { + $cardType.attr('class', 'icon-type'); + self.selectedCardType(null); + } - if (event.type !== 'fieldStateChange') { + }); - return false; - } + hostedFieldsInstance.on('validityChange', function (event) { + var field = event.fields[event.emittedBy], + fieldKey = event.emittedBy; - // Handle a change in validation or card type - if (event.target.fieldKey === 'number') { - self.selectedCardType(null); - } + if (fieldKey === 'number') { + $cardType.addClass('icon-type-' + event.cards[0].type); + } + + if (fieldKey in self.selectorsMapper && field.isValid === false) { + self.addInvalidClass(self.selectorsMapper[fieldKey]); + } + }); + + hostedFieldsInstance.on('blur', function (event) { + if (event.emittedBy === 'number') { + self.validateCardType(); + } + }); - // remove previously set classes - $cardType.attr('class', 'icon-type'); + hostedFieldsInstance.on('cardTypeChange', function (event) { + if (event.cards.length !== 1) { + return; + } - if (event.card) { - $cardType.addClass('icon-type-' + event.card.type); + $cardType.addClass('icon-type-' + event.cards[0].type); self.selectedCardType( - validator.getMageCardType(event.card.type, self.getCcAvailableTypes()) + validator.getMageCardType(event.cards[0].type, self.getCcAvailableTypes()) ); - } + }); }, /** @@ -298,16 +291,32 @@ define([ * Trigger order submit */ submitOrder: function () { - this.$selector.validate().form(); - this.$selector.trigger('afterValidate.beforeSubmit'); - $('body').trigger('processStop'); + var self = this; + + self.$selector.validate().form(); + self.$selector.trigger('afterValidate.beforeSubmit'); // validate parent form - if (this.$selector.validate().errorList.length) { + if (self.$selector.validate().errorList.length) { + $('body').trigger('processStop'); + return false; } - $('#' + this.container).find('[type="submit"]').trigger('click'); + if (!self.validateCardType()) { + return false; + } + + self.hostedFieldsInstance.tokenize(function (err, payload) { + if (err) { + self.error($t('Some payment input fields are invalid.')); + + return false; + } + + self.setPaymentDetails(payload.nonce); + $('#' + self.container).find('[type="submit"]').trigger('click'); + }); }, /** @@ -337,12 +346,10 @@ define([ * @returns {Boolean} */ validateCardType: function () { - var $input = $(this.getSelector('cc_number')); - - $input.removeClass('braintree-hosted-fields-invalid'); + this.removeInvalidClass('cc_number'); if (!this.selectedCardType()) { - $input.addClass('braintree-hosted-fields-invalid'); + this.addInvalidClass('cc_number'); return false; } @@ -358,6 +365,28 @@ define([ */ getSelector: function (field) { return '#' + this.code + '_' + field; + }, + + /** + * Add invalid class to field. + * + * @param {String} field + * @returns void + * @private + */ + addInvalidClass: function (field) { + $(this.getSelector(field)).addClass('braintree-hosted-fields-invalid'); + }, + + /** + * Remove invalid class from field. + * + * @param {String} field + * @returns void + * @private + */ + removeInvalidClass: function (field) { + $(this.getSelector(field)).removeClass('braintree-hosted-fields-invalid'); } }); }); diff --git a/app/code/Magento/Braintree/view/frontend/requirejs-config.js b/app/code/Magento/Braintree/view/frontend/requirejs-config.js index 9fc38064677ef..5c9bcd88de730 100644 --- a/app/code/Magento/Braintree/view/frontend/requirejs-config.js +++ b/app/code/Magento/Braintree/view/frontend/requirejs-config.js @@ -6,7 +6,19 @@ var config = { map: { '*': { - braintree: 'https://js.braintreegateway.com/js/braintree-2.32.0.min.js' + braintreeClient: 'https://js.braintreegateway.com/web/3.48.0/js/client.min.js', + braintreeHostedFields: 'https://js.braintreegateway.com/web/3.48.0/js/hosted-fields.min.js', + braintreePayPal: 'https://js.braintreegateway.com/web/3.48.0/js/paypal-checkout.min.js', + braintree3DSecure: 'https://js.braintreegateway.com/web/3.48.0/js/three-d-secure.min.js', + braintreeDataCollector: 'https://js.braintreegateway.com/web/3.48.0/js/data-collector.min.js' + } + }, + paths: { + braintreePayPalCheckout: 'https://www.paypalobjects.com/api/checkout.min' + }, + shim: { + braintreePayPalCheckout: { + exports: 'paypal' } } }; diff --git a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml index bf8aa8dd09c2c..fc3030b6a4b36 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml @@ -15,7 +15,7 @@ }; layout([ { - component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/hosted-fields', + component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/cc-form', name: 'payment_method_braintree', method: paymentMethodData.method, item: paymentMethodData diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml index 98bba7d6044cb..36eddcf5819d9 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml @@ -15,21 +15,18 @@ $config = [ 'id' => $id, 'clientToken' => $block->getClientToken(), 'displayName' => $block->getMerchantName(), - 'actionSuccess' => $block->getActionSuccess() + 'actionSuccess' => $block->getActionSuccess(), + 'environment' => $block->getEnvironment() ] ]; ?> -<div data-mage-init='<?= /* @noEscape */ json_encode($config) ?>' - class="paypal checkout paypal-logo braintree-paypal-logo<?= /* @noEscape */ $block->getContainerId() ?>-container"> - <button data-currency="<?= /* @noEscape */ $block->getCurrency() ?>" - data-locale="<?= /* @noEscape */ $block->getLocale() ?>" - data-amount="<?= /* @noEscape */ $block->getAmount() ?>" - id="<?= /* @noEscape */ $id ?>" - class="action-braintree-paypal-logo" disabled> - <img class="braintree-paypal-button-hidden" - src="https://checkout.paypal.com/pwpp/2.17.6/images/pay-with-paypal.png" - alt="<?= $block->escapeHtml(__('Pay with PayPal')) ?>" - title="<?= $block->escapeHtml(__('Pay with PayPal')) ?>"/> - </button> +<div data-mage-init='<?= /* @noEscape */ json_encode($config); ?>' + class="paypal checkout paypal-logo braintree-paypal-logo<?= /* @noEscape */ $block->getContainerId(); ?>-container"> + <div data-currency="<?= /* @noEscape */ $block->getCurrency(); ?>" + data-locale="<?= /* @noEscape */ $block->getLocale(); ?>" + data-amount="<?= /* @noEscape */ $block->getAmount(); ?>" + id="<?= /* @noEscape */ $id; ?>" + class="action-braintree-paypal-logo"> + </div> </div> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js index 3ac50fbcb47cc..aacd3016d7367 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js @@ -9,7 +9,9 @@ define( 'uiComponent', 'underscore', 'jquery', - 'braintree', + 'braintreeClient', + 'braintreePayPal', + 'braintreePayPalCheckout', 'Magento_Braintree/js/paypal/form-builder', 'domReady!' ], @@ -19,7 +21,9 @@ define( Component, _, $, - braintree, + braintreeClient, + braintreePayPal, + braintreePayPalCheckout, formBuilder ) { 'use strict'; @@ -27,58 +31,34 @@ define( return Component.extend({ defaults: { - - integrationName: 'braintreePaypal.currentIntegration', - - /** - * {String} - */ displayName: null, - - /** - * {String} - */ clientToken: null, - - /** - * {Object} - */ - clientConfig: { - - /** - * @param {Object} integration - */ - onReady: function (integration) { - resolver(function () { - registry.set(this.integrationName, integration); - $('#' + this.id).removeAttr('disabled'); - }, this); - }, - - /** - * @param {Object} payload - */ - onPaymentMethodReceived: function (payload) { - $('body').trigger('processStart'); - - formBuilder.build( - { - action: this.actionSuccess, - fields: { - result: JSON.stringify(payload) - } - } - ).submit(); - } - } + paypalCheckoutInstance: null }, /** * @returns {Object} */ initialize: function () { - this._super() - .initComponent(); + var self = this; + + self._super(); + + braintreeClient.create({ + authorization: self.clientToken + }) + .then(function (clientInstance) { + return braintreePayPal.create({ + client: clientInstance + }); + }) + .then(function (paypalCheckoutInstance) { + self.paypalCheckoutInstance = paypalCheckoutInstance; + + return self.paypalCheckoutInstance; + }); + + self.initComponent(); return this; }, @@ -87,64 +67,76 @@ define( * @returns {Object} */ initComponent: function () { - var currentIntegration = registry.get(this.integrationName), - $this = $('#' + this.id), - self = this, + var self = this, + selector = '#' + self.id, + $this = $(selector), data = { amount: $this.data('amount'), locale: $this.data('locale'), currency: $this.data('currency') + }; + + $this.html(''); + braintreePayPalCheckout.Button.render({ + env: self.environment, + style: { + color: 'blue', + shape: 'rect', + size: 'medium', + label: 'pay', + tagline: false + }, + + /** + * Payment setup + */ + payment: function () { + return self.paypalCheckoutInstance.createPayment(self.getClientConfig(data)); }, - initCallback = function () { - $this.attr('disabled', 'disabled'); - registry.remove(this.integrationName); - braintree.setup(this.clientToken, 'custom', this.getClientConfig(data)); - - $this.off('click') - .on('click', function (event) { - event.preventDefault(); - - registry.get(self.integrationName, function (integration) { - try { - integration.paypal.initAuthFlow(); - } catch (e) { - $this.attr('disabled', 'disabled'); + + /** + * Triggers on `onAuthorize` event + * + * @param {Object} response + */ + onAuthorize: function (response) { + return self.paypalCheckoutInstance.tokenizePayment(response) + .then(function (payload) { + $('body').trigger('processStart'); + + formBuilder.build( + { + action: self.actionSuccess, + fields: { + result: JSON.stringify(payload) + } } - }); + ).submit(); }); - }.bind(this); - - currentIntegration ? - currentIntegration.teardown(initCallback) : - initCallback(); + } + }, selector); return this; }, /** * @returns {Object} + * @private */ getClientConfig: function (data) { - this.clientConfig.paypal = { - singleUse: true, + var config = { + flow: 'checkout', amount: data.amount, currency: data.currency, locale: data.locale, - enableShippingAddress: true, - headless: true + enableShippingAddress: true }; if (this.displayName) { - this.clientConfig.paypal.displayName = this.displayName; + config.displayName = this.displayName; } - _.each(this.clientConfig, function (fn, name) { - if (typeof fn === 'function') { - this.clientConfig[name] = fn.bind(this); - } - }, this); - - return this.clientConfig; + return config; } }); } diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/3d-secure.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/3d-secure.js index e3b806bf21384..43aec27508ce9 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/3d-secure.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/3d-secure.js @@ -7,17 +7,59 @@ define([ 'jquery', + 'braintree3DSecure', 'Magento_Braintree/js/view/payment/adapter', 'Magento_Checkout/js/model/quote', - 'mage/translate' -], function ($, braintree, quote, $t) { + 'mage/translate', + 'Magento_Ui/js/modal/modal', + 'Magento_Checkout/js/model/full-screen-loader' +], function ( + $, + braintree3DSecure, + braintreeAdapter, + quote, + $t, + Modal, + fullScreenLoader +) { 'use strict'; return { config: null, + modal: null, + threeDSecureInstance: null, + state: null, /** - * Set 3d secure config + * Initializes component + */ + initialize: function () { + var self = this, + promise = $.Deferred(); + + self.state = $.Deferred(); + braintreeAdapter.getApiClient() + .then(function (clientInstance) { + return braintree3DSecure.create({ + version: 2, // Will use 3DS 2 whenever possible + client: clientInstance + }); + }) + .then(function (threeDSecureInstance) { + self.threeDSecureInstance = threeDSecureInstance; + promise.resolve(self.threeDSecureInstance); + }) + .catch(function (err) { + fullScreenLoader.stopLoader(); + promise.reject(err); + }); + + return promise.promise(); + }, + + /** + * Sets 3D Secure config + * * @param {Object} config */ setConfig: function (config) { @@ -26,7 +68,8 @@ define([ }, /** - * Get code + * Gets code + * * @returns {String} */ getCode: function () { @@ -34,54 +77,105 @@ define([ }, /** - * Validate Braintree payment nonce + * Validates 3D Secure + * * @param {Object} context * @returns {Object} */ validate: function (context) { - var client = braintree.getApiClient(), - state = $.Deferred(), + var self = this, totalAmount = quote.totals()['base_grand_total'], - billingAddress = quote.billingAddress(); - - if (!this.isAmountAvailable(totalAmount) || !this.isCountryAvailable(billingAddress.countryId)) { - state.resolve(); + billingAddress = quote.billingAddress(), + shippingAddress = quote.shippingAddress(), + options = { + amount: totalAmount, + nonce: context.paymentPayload.nonce, + billingAddress: { + givenName: billingAddress.firstname, + surname: billingAddress.lastname, + phoneNumber: billingAddress.telephone, + streetAddress: billingAddress.street[0], + extendedAddress: billingAddress.street[1], + locality: billingAddress.city, + region: billingAddress.regionCode, + postalCode: billingAddress.postcode, + countryCodeAlpha2: billingAddress.countryId + }, + + /** + * Will be called after receiving ThreeDSecure response, before completing the flow. + * + * @param {Object} data - ThreeDSecure data to consume before continuing + * @param {Function} next - callback to continue flow + */ + onLookupComplete: function (data, next) { + next(); + } + }; - return state.promise(); + if (context.paymentPayload.details) { + options.bin = context.paymentPayload.details.bin; } - client.verify3DS({ - amount: totalAmount, - creditCard: context.paymentMethodNonce - }, function (error, response) { - var liability; - - if (error) { - state.reject(error.message); - - return; - } - - liability = { - shifted: response.verificationDetails.liabilityShifted, - shiftPossible: response.verificationDetails.liabilityShiftPossible + if (shippingAddress) { + options.additionalInformation = { + shippingGivenName: shippingAddress.firstname, + shippingSurname: shippingAddress.lastname, + shippingPhone: shippingAddress.telephone, + shippingAddress: { + streetAddress: shippingAddress.street[0], + extendedAddress: shippingAddress.street[1], + locality: shippingAddress.city, + region: shippingAddress.regionCode, + postalCode: shippingAddress.postcode, + countryCodeAlpha2: shippingAddress.countryId + } }; + } - if (liability.shifted || !liability.shifted && !liability.shiftPossible) { - context.paymentMethodNonce = response.nonce; - state.resolve(); - } else { - state.reject($t('Please try again with another form of payment.')); - } - }); + if (!this.isAmountAvailable(totalAmount) || !this.isCountryAvailable(billingAddress.countryId)) { + self.state = $.Deferred(); + self.state.resolve(); + + return self.state.promise(); + } - return state.promise(); + fullScreenLoader.startLoader(); + this.initialize() + .then(function () { + self.threeDSecureInstance.verifyCard(options, function (err, payload) { + if (err) { + fullScreenLoader.stopLoader(); + self.state.reject(err.message); + + return; + } + + // `liabilityShifted` indicates that 3DS worked and authentication succeeded + // if `liabilityShifted` and `liabilityShiftPossible` are false - card is ineligible for 3DS + if (payload.liabilityShifted || !payload.liabilityShifted && !payload.liabilityShiftPossible) { + context.paymentPayload.nonce = payload.nonce; + self.state.resolve(); + } else { + fullScreenLoader.stopLoader(); + self.state.reject($t('Please try again with another form of payment.')); + } + }); + }) + .fail(function () { + fullScreenLoader.stopLoader(); + self.state.reject($t('Please try again with another form of payment.')); + }); + + return self.state.promise(); }, /** - * Check minimal amount for 3d secure activation + * Checks minimal amount for 3D Secure activation + * * @param {Number} amount * @returns {Boolean} + * @private */ isAmountAvailable: function (amount) { amount = parseFloat(amount); @@ -90,9 +184,11 @@ define([ }, /** - * Check if current country is available for 3d secure + * Checks if current country is available for 3D Secure + * * @param {String} countryId * @returns {Boolean} + * @private */ isCountryAvailable: function (countryId) { var key, diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/adapter.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/adapter.js index 185e347bc9fd1..9cd6aa688674e 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/adapter.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/adapter.js @@ -6,81 +6,42 @@ /*global define*/ define([ 'jquery', - 'braintree', - 'Magento_Ui/js/model/messageList', - 'mage/translate' -], function ($, braintree, globalMessageList, $t) { + 'braintreeClient' +], function ($, braintreeClient) { 'use strict'; return { apiClient: null, - config: {}, checkout: null, + code: 'braintree', /** - * Get Braintree api client + * Returns Braintree API client * @returns {Object} */ getApiClient: function () { - if (!this.apiClient) { - this.apiClient = new braintree.api.Client({ - clientToken: this.getClientToken() - }); - } - - return this.apiClient; - }, - - /** - * Set configuration - * @param {Object} config - */ - setConfig: function (config) { - this.config = config; - }, - - /** - * Setup Braintree SDK - */ - setup: function () { - if (!this.getClientToken()) { - this.showError($t('Sorry, but something went wrong.')); - } - - braintree.setup(this.getClientToken(), 'custom', this.config); + return braintreeClient.create({ + authorization: this.getClientToken() + }); }, /** - * Get payment name + * Returns payment code + * * @returns {String} */ getCode: function () { - return 'braintree'; + return this.code; }, /** - * Get client token - * @returns {String|*} - */ - getClientToken: function () { - - return window.checkoutConfig.payment[this.getCode()].clientToken; - }, - - /** - * Show error message + * Returns client token * - * @param {String} errorMessage - */ - showError: function (errorMessage) { - globalMessageList.addErrorMessage({ - message: errorMessage - }); - }, - - /** - * May be triggered on Braintree SDK setup + * @returns {String} + * @private */ - onReady: function () {} + getClientToken: function () { + return window.checkoutConfig.payment[this.code].clientToken; + } }; }); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/braintree.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/braintree.js index 2e1c65632e85f..132fcd6b3b06c 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/braintree.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/braintree.js @@ -23,7 +23,7 @@ define( rendererList.push( { type: braintreeType, - component: 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields' + component: 'Magento_Braintree/js/view/payment/method-renderer/cc-form' } ); } diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/kount.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/kount.js new file mode 100644 index 0000000000000..cd0d024387b8c --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/kount.js @@ -0,0 +1,61 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ + +define([ + 'jquery', + 'braintreeDataCollector', + 'Magento_Braintree/js/view/payment/adapter' +], function ( + $, + braintreeDataCollector, + braintreeAdapter +) { + 'use strict'; + + return { + paymentCode: 'braintree', + + /** + * Returns information about a customer's device on checkout page for passing to Kount for review. + * + * @returns {Object} + */ + getDeviceData: function () { + var state = $.Deferred(); + + if (this.hasFraudProtection()) { + braintreeAdapter.getApiClient() + .then(function (clientInstance) { + return braintreeDataCollector.create({ + client: clientInstance, + kount: true + }); + }) + .then(function (dataCollectorInstance) { + var deviceData = dataCollectorInstance.deviceData; + + state.resolve(deviceData); + }) + .catch(function (err) { + state.reject(err); + }); + } + + return state.promise(); + }, + + /** + * Returns setting value. + * + * @returns {Boolean} + * @private + */ + hasFraudProtection: function () { + return window.checkoutConfig.payment[this.paymentCode].hasFraudProtection; + } + }; +}); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js index 39bdf582c8cd7..5f06d26e2acfc 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js @@ -9,90 +9,97 @@ define( 'underscore', 'jquery', 'Magento_Payment/js/view/payment/cc-form', - 'Magento_Checkout/js/model/quote', 'Magento_Braintree/js/view/payment/adapter', - 'mage/translate', + 'braintreeHostedFields', + 'Magento_Checkout/js/model/quote', 'Magento_Braintree/js/validator', + 'Magento_Ui/js/model/messageList', 'Magento_Braintree/js/view/payment/validator-handler', - 'Magento_Checkout/js/model/full-screen-loader' + 'Magento_Vault/js/view/payment/vault-enabler', + 'Magento_Braintree/js/view/payment/kount', + 'mage/translate', + 'prototype', + 'domReady!' ], function ( _, $, Component, + braintreeAdapter, + hostedFields, quote, - braintree, - $t, validator, + globalMessageList, validatorManager, - fullScreenLoader + VaultEnabler, + kount, + $t ) { 'use strict'; return Component.extend({ defaults: { + template: 'Magento_Braintree/payment/form', active: false, - braintreeClient: null, - braintreeDeviceData: null, - paymentMethodNonce: null, - lastBillingAddress: null, - ccCode: null, - ccMessageContainer: null, - validatorManager: validatorManager, code: 'braintree', - - /** - * Additional payment data - * - * {Object} - */ - additionalData: {}, - - /** - * Braintree client configuration - * - * {Object} - */ - clientConfig: { - - /** - * Triggers on payment nonce receive - * @param {Object} response - */ - onPaymentMethodReceived: function (response) { - this.beforePlaceOrder(response); - }, - - /** - * Device data initialization - * - * @param {Object} checkout - */ - onReady: function (checkout) { - braintree.checkout = checkout; - braintree.onReady(); - }, - - /** - * Triggers on any Braintree error - * @param {Object} response - */ - onError: function (response) { - braintree.showError($t('Payment ' + this.getTitle() + ' can\'t be initialized')); - this.isPlaceOrderActionAllowed(true); - throw response.message; - }, - - /** - * Triggers when customer click "Cancel" - */ - onCancelled: function () { - this.paymentMethodNonce = null; - } + lastBillingAddress: null, + hostedFieldsInstance: null, + selectorsMapper: { + 'expirationMonth': 'expirationMonth', + 'expirationYear': 'expirationYear', + 'number': 'cc_number', + 'cvv': 'cc_cid' }, - imports: { - onActiveChange: 'active' - } + paymentPayload: { + nonce: null + }, + additionalData: {} + }, + + /** + * @returns {exports.initialize} + */ + initialize: function () { + var self = this; + + self._super(); + self.vaultEnabler = new VaultEnabler(); + self.vaultEnabler.setPaymentCode(self.getVaultCode()); + + kount.getDeviceData() + .then(function (deviceData) { + self.additionalData['device_data'] = deviceData; + }); + + return self; + }, + + /** + * Init hosted fields. + * + * Is called after knockout finishes input fields bindings. + */ + initHostedFields: function () { + var self = this; + + braintreeAdapter.getApiClient() + .then(function (clientInstance) { + + return hostedFields.create({ + client: clientInstance, + fields: self.getFieldsConfiguration() + }); + }) + .then(function (hostedFieldsInstance) { + self.hostedFieldsInstance = hostedFieldsInstance; + self.isPlaceOrderActionAllowed(true); + self.initFormValidationEvents(hostedFieldsInstance); + + return self.hostedFieldsInstance; + }) + .catch(function () { + self.showError($t('Payment ' + self.getTitle() + ' can\'t be initialized')); + }); }, /** @@ -104,8 +111,6 @@ define( validator.setConfig(window.checkoutConfig.payment[this.getCode()]); this._super() .observe(['active']); - this.validatorManager.initialize(); - this.initClientConfig(); return this; }, @@ -133,225 +138,290 @@ define( }, /** - * Triggers when payment method change - * @param {Boolean} isActive + * Get data + * + * @returns {Object} */ - onActiveChange: function (isActive) { - if (!isActive) { - return; - } + getData: function () { + var data = { + 'method': this.getCode(), + 'additional_data': { + 'payment_method_nonce': this.paymentPayload.nonce + } + }; - this.restoreMessageContainer(); - this.restoreCode(); + data['additional_data'] = _.extend(data['additional_data'], this.additionalData); + this.vaultEnabler.visitAdditionalData(data); - /** - * Define onReady callback - */ - braintree.onReady = function () {}; - this.initBraintree(); + return data; }, /** - * Restore original message container for cc-form component + * Get list of available CC types + * + * @returns {Object} */ - restoreMessageContainer: function () { - this.messageContainer = this.ccMessageContainer; + getCcAvailableTypes: function () { + var availableTypes = validator.getAvailableCardTypes(), + billingAddress = quote.billingAddress(), + billingCountryId; + + this.lastBillingAddress = quote.shippingAddress(); + + if (!billingAddress) { + billingAddress = this.lastBillingAddress; + } + + billingCountryId = billingAddress.countryId; + + if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { + return validator.collectTypes( + availableTypes, + validator.getCountrySpecificCardTypes(billingCountryId) + ); + } + + return availableTypes; }, /** - * Restore original code for cc-form component + * @returns {Boolean} */ - restoreCode: function () { - this.code = this.ccCode; + isVaultEnabled: function () { + return this.vaultEnabler.isVaultEnabled(); }, - /** @inheritdoc */ - initChildren: function () { - this._super(); - this.ccMessageContainer = this.messageContainer; - this.ccCode = this.code; - - return this; + /** + * Returns vault code. + * + * @returns {String} + */ + getVaultCode: function () { + return window.checkoutConfig.payment[this.getCode()].ccVaultCode; }, /** - * Init config + * Action to place order + * @param {String} key */ - initClientConfig: function () { - // Advanced fraud tools settings - if (this.hasFraudProtection()) { - this.clientConfig = _.extend(this.clientConfig, this.kountConfig()); + placeOrder: function (key) { + var self = this; + + if (key) { + return self._super(); } + // place order on success validation + validatorManager.validate(self, function () { + return self.placeOrder('parent'); + }, function (err) { - _.each(this.clientConfig, function (fn, name) { - if (typeof fn === 'function') { - this.clientConfig[name] = fn.bind(this); + if (err) { + self.showError(err); } - }, this); + }); + + return false; }, /** - * Init Braintree configuration + * Returns state of place order button + * + * @returns {Boolean} */ - initBraintree: function () { - var intervalId = setInterval(function () { - // stop loader when frame will be loaded - if ($('#braintree-hosted-field-number').length) { - clearInterval(intervalId); - fullScreenLoader.stopLoader(); - } - }, 500); - - if (braintree.checkout) { - braintree.checkout.teardown(function () { - braintree.checkout = null; - }); - } - - fullScreenLoader.startLoader(); - braintree.setConfig(this.clientConfig); - braintree.setup(); + isButtonActive: function () { + return this.isActive() && this.isPlaceOrderActionAllowed(); }, /** - * @returns {Object} + * Trigger order placing */ - kountConfig: function () { - var config = { - dataCollector: { - kount: { - environment: this.getEnvironment() + placeOrderClick: function () { + var self = this; + + if (this.isFormValid(this.hostedFieldsInstance)) { + self.hostedFieldsInstance.tokenize(function (err, payload) { + if (err) { + self.showError($t('Some payment input fields are invalid.')); + + return; } - }, - - /** - * Device data initialization - * - * @param {Object} checkout - */ - onReady: function (checkout) { - braintree.checkout = checkout; - this.additionalData['device_data'] = checkout.deviceData; - braintree.onReady(); - } - }; - if (this.getKountMerchantId()) { - config.dataCollector.kount.merchantId = this.getKountMerchantId(); + if (self.validateCardType()) { + self.setPaymentPayload(payload); + self.placeOrder(); + } + }); } - - return config; }, /** - * Get full selector name + * Validates credit card form. * - * @param {String} field - * @returns {String} + * @param {Object} hostedFieldsInstance + * @returns {Boolean} + * @private */ - getSelector: function (field) { - return '#' + this.getCode() + '_' + field; + isFormValid: function (hostedFieldsInstance) { + var self = this, + state = hostedFieldsInstance.getState(); + + return Object.keys(state.fields).every(function (fieldKey) { + if (fieldKey in self.selectorsMapper && state.fields[fieldKey].isValid === false) { + self.addInvalidClass(self.selectorsMapper[fieldKey]); + } + + return state.fields[fieldKey].isValid; + }); }, /** - * Get list of available CC types + * Init form validation events. * - * @returns {Object} + * @param {Object} hostedFieldsInstance + * @private */ - getCcAvailableTypes: function () { - var availableTypes = validator.getAvailableCardTypes(), - billingAddress = quote.billingAddress(), - billingCountryId; + initFormValidationEvents: function (hostedFieldsInstance) { + var self = this; - this.lastBillingAddress = quote.shippingAddress(); + hostedFieldsInstance.on('empty', function (event) { + if (event.emittedBy === 'number') { + self.selectedCardType(null); + } - if (!billingAddress) { - billingAddress = this.lastBillingAddress; - } + }); - billingCountryId = billingAddress.countryId; + hostedFieldsInstance.on('blur', function (event) { + if (event.emittedBy === 'number') { + self.validateCardType(); + } + }); - if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { - return validator.collectTypes( - availableTypes, validator.getCountrySpecificCardTypes(billingCountryId) - ); - } + hostedFieldsInstance.on('validityChange', function (event) { + var field = event.fields[event.emittedBy], + fieldKey = event.emittedBy; - return availableTypes; + if (fieldKey === 'number') { + self.isValidCardNumber = field.isValid; + } + + if (fieldKey in self.selectorsMapper && field.isValid === false) { + self.addInvalidClass(self.selectorsMapper[fieldKey]); + } + }); + + hostedFieldsInstance.on('cardTypeChange', function (event) { + if (event.cards.length === 1) { + self.selectedCardType( + validator.getMageCardType(event.cards[0].type, self.getCcAvailableTypes()) + ); + } + }); }, /** - * @returns {Boolean} + * Get full selector name + * + * @param {String} field + * @returns {String} + * @private */ - hasFraudProtection: function () { - return window.checkoutConfig.payment[this.getCode()].hasFraudProtection; + getSelector: function (field) { + return '#' + this.getCode() + '_' + field; }, /** - * @returns {String} + * Add invalid class to field. + * + * @param {String} field + * @returns void + * @private */ - getEnvironment: function () { - return window.checkoutConfig.payment[this.getCode()].environment; + addInvalidClass: function (field) { + $(this.getSelector(field)).addClass('braintree-hosted-fields-invalid'); }, /** - * @returns {String} + * Remove invalid class from field. + * + * @param {String} field + * @returns void + * @private */ - getKountMerchantId: function () { - return window.checkoutConfig.payment[this.getCode()].kountMerchantId; + removeInvalidClass: function (field) { + $(this.getSelector(field)).removeClass('braintree-hosted-fields-invalid'); }, /** - * Get data + * Get Braintree Hosted Fields * * @returns {Object} + * @private */ - getData: function () { - var data = { - 'method': this.getCode(), - 'additional_data': { - 'payment_method_nonce': this.paymentMethodNonce - } - }; + getFieldsConfiguration: function () { + var self = this, + fields = { + number: { + selector: self.getSelector('cc_number') + }, + expirationMonth: { + selector: self.getSelector('expirationMonth'), + placeholder: $t('MM') + }, + expirationYear: { + selector: self.getSelector('expirationYear'), + placeholder: $t('YY') + } + }; - data['additional_data'] = _.extend(data['additional_data'], this.additionalData); + if (self.hasVerification()) { + fields.cvv = { + selector: self.getSelector('cc_cid') + }; + } - return data; + return fields; }, /** - * Set payment nonce - * @param {String} paymentMethodNonce + * Validate current credit card type. + * + * @returns {Boolean} + * @private */ - setPaymentMethodNonce: function (paymentMethodNonce) { - this.paymentMethodNonce = paymentMethodNonce; + validateCardType: function () { + var cardFieldName = 'cc_number'; + + this.removeInvalidClass(cardFieldName); + + if (this.selectedCardType() === null || !this.isValidCardNumber) { + this.addInvalidClass(cardFieldName); + + return false; + } + + return true; }, /** - * Prepare data to place order - * @param {Object} data + * Sets payment payload + * + * @param {Object} paymentPayload + * @private */ - beforePlaceOrder: function (data) { - this.setPaymentMethodNonce(data.nonce); - this.placeOrder(); + setPaymentPayload: function (paymentPayload) { + this.paymentPayload = paymentPayload; }, /** - * Action to place order - * @param {String} key + * Show error message + * + * @param {String} errorMessage + * @private */ - placeOrder: function (key) { - var self = this; - - if (key) { - return self._super(); - } - // place order on success validation - self.validatorManager.validate(self, function () { - return self.placeOrder('parent'); + showError: function (errorMessage) { + globalMessageList.addErrorMessage({ + message: errorMessage }); - - return false; } }); } diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js deleted file mode 100644 index 9e496e43b27c5..0000000000000 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -/*browser:true*/ -/*global define*/ - -define([ - 'jquery', - 'Magento_Braintree/js/view/payment/method-renderer/cc-form', - 'Magento_Braintree/js/validator', - 'Magento_Vault/js/view/payment/vault-enabler', - 'mage/translate', - 'Magento_Checkout/js/model/payment/additional-validators' -], function ($, Component, validator, VaultEnabler, $t, additionalValidators) { - 'use strict'; - - return Component.extend({ - - defaults: { - template: 'Magento_Braintree/payment/form', - clientConfig: { - - /** - * {String} - */ - id: 'co-transparent-form-braintree' - }, - isValidCardNumber: false - }, - - /** - * @returns {exports.initialize} - */ - initialize: function () { - this._super(); - this.vaultEnabler = new VaultEnabler(); - this.vaultEnabler.setPaymentCode(this.getVaultCode()); - - return this; - }, - - /** - * Init config - */ - initClientConfig: function () { - this._super(); - - // Hosted fields settings - this.clientConfig.hostedFields = this.getHostedFields(); - }, - - /** - * @returns {Object} - */ - getData: function () { - var data = this._super(); - - this.vaultEnabler.visitAdditionalData(data); - - return data; - }, - - /** - * @returns {Boolean} - */ - isVaultEnabled: function () { - return this.vaultEnabler.isVaultEnabled(); - }, - - /** - * Get Braintree Hosted Fields - * @returns {Object} - */ - getHostedFields: function () { - var self = this, - fields = { - number: { - selector: self.getSelector('cc_number') - }, - expirationMonth: { - selector: self.getSelector('expirationMonth'), - placeholder: $t('MM') - }, - expirationYear: { - selector: self.getSelector('expirationYear'), - placeholder: $t('YY') - } - }; - - if (self.hasVerification()) { - fields.cvv = { - selector: self.getSelector('cc_cid') - }; - } - - /** - * Triggers on Hosted Field changes - * @param {Object} event - * @returns {Boolean} - */ - fields.onFieldEvent = function (event) { - if (event.isEmpty === false) { - self.validateCardType(); - } - - if (event.type !== 'fieldStateChange') { - return false; - } - - // Handle a change in validation or card type - if (event.target.fieldKey === 'number') { - self.selectedCardType(null); - } - - if (event.target.fieldKey === 'number' && event.card) { - self.isValidCardNumber = event.isValid; - self.selectedCardType( - validator.getMageCardType(event.card.type, self.getCcAvailableTypes()) - ); - } - }; - - return fields; - }, - - /** - * Validate current credit card type - * @returns {Boolean} - */ - validateCardType: function () { - var $selector = $(this.getSelector('cc_number')), - invalidClass = 'braintree-hosted-fields-invalid'; - - $selector.removeClass(invalidClass); - - if (this.selectedCardType() === null || !this.isValidCardNumber) { - $(this.getSelector('cc_number')).addClass(invalidClass); - - return false; - } - - return true; - }, - - /** - * Returns state of place order button - * @returns {Boolean} - */ - isButtonActive: function () { - return this.isActive() && this.isPlaceOrderActionAllowed(); - }, - - /** - * Trigger order placing - */ - placeOrderClick: function () { - if (this.validateCardType() && additionalValidators.validate()) { - this.isPlaceOrderActionAllowed(false); - $(this.getSelector('submit')).trigger('click'); - } - }, - - /** - * @returns {String} - */ - getVaultCode: function () { - return window.checkoutConfig.payment[this.getCode()].ccVaultCode; - } - }); -}); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/cc-form.js similarity index 59% rename from app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js rename to app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/cc-form.js index 1ceebc8e66282..868fe174ae482 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/cc-form.js @@ -7,13 +7,14 @@ define([ 'jquery', - 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields', + 'Magento_Braintree/js/view/payment/method-renderer/cc-form', 'Magento_Braintree/js/validator', 'Magento_Ui/js/model/messageList', 'mage/translate', 'Magento_Checkout/js/model/full-screen-loader', - 'Magento_Checkout/js/action/set-payment-information', - 'Magento_Checkout/js/model/payment/additional-validators' + 'Magento_Checkout/js/action/set-payment-information-extended', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Braintree/js/view/payment/validator-handler' ], function ( $, Component, @@ -21,8 +22,9 @@ define([ messageList, $t, fullScreenLoader, - setPaymentInformationAction, - additionalValidators + setPaymentInformationExtended, + additionalValidators, + validatorManager ) { 'use strict'; @@ -31,33 +33,13 @@ define([ template: 'Magento_Braintree/payment/multishipping/form' }, - /** - * Get list of available CC types - * - * @returns {Object} - */ - getCcAvailableTypes: function () { - var availableTypes = validator.getAvailableCardTypes(), - billingCountryId; - - billingCountryId = $('#multishipping_billing_country_id').val(); - - if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { - return validator.collectTypes( - availableTypes, validator.getCountrySpecificCardTypes(billingCountryId) - ); - } - - return availableTypes; - }, - /** * @override */ placeOrder: function () { var self = this; - this.validatorManager.validate(self, function () { + validatorManager.validate(self, function () { return self.setPaymentInformation(); }); }, @@ -67,13 +49,12 @@ define([ */ setPaymentInformation: function () { if (additionalValidators.validate()) { - fullScreenLoader.startLoader(); - $.when( - setPaymentInformationAction( + setPaymentInformationExtended( this.messageContainer, - this.getData() + this.getData(), + true ) ).done(this.done.bind(this)) .fail(this.fail.bind(this)); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js index 6702e58d1214b..b3837103148cc 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js @@ -8,34 +8,58 @@ define([ 'jquery', 'underscore', 'Magento_Braintree/js/view/payment/method-renderer/paypal', - 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/action/set-payment-information-extended', 'Magento_Checkout/js/model/payment/additional-validators', - 'Magento_Checkout/js/model/full-screen-loader', - 'mage/translate' + 'Magento_Checkout/js/model/full-screen-loader' ], function ( $, _, Component, - setPaymentInformationAction, + setPaymentInformationExtended, additionalValidators, - fullScreenLoader, - $t + fullScreenLoader ) { 'use strict'; return Component.extend({ defaults: { template: 'Magento_Braintree/payment/multishipping/paypal', - submitButtonSelector: '#payment-continue span' + submitButtonSelector: '#payment-continue span', + paypalButtonSelector: '[id="parent-payment-continue"]', + reviewButtonHtml: '' }, /** * @override */ - onActiveChange: function (isActive) { - this.updateSubmitButtonTitle(isActive); + initObservable: function () { + this.reviewButtonHtml = $(this.paypalButtonSelector).html(); + + return this._super(); + }, + /** + * Get configuration for PayPal. + * + * @returns {Object} + */ + getPayPalConfig: function () { + var config; + + config = this._super(); + config.flow = 'vault'; + config.enableShippingAddress = false; + config.shippingAddressEditable = false; + + return config; + }, + + /** + * @override + */ + onActiveChange: function (isActive) { this._super(isActive); + this.updateSubmitButton(isActive); }, /** @@ -44,7 +68,7 @@ define([ beforePlaceOrder: function (data) { this._super(data); - this.updateSubmitButtonTitle(true); + this.updateSubmitButton(true); }, /** @@ -87,38 +111,33 @@ define([ * @returns {Boolean} */ isPaymentMethodNonceReceived: function () { - return this.paymentMethodNonce !== null; + return this.paymentPayload.nonce !== null; }, /** - * Updates submit button title on multi-addresses checkout billing form. + * Updates submit button on multi-addresses checkout billing form. * * @param {Boolean} isActive */ - updateSubmitButtonTitle: function (isActive) { - var title = this.isPaymentMethodNonceReceived() || !isActive ? - $t('Go to Review Your Order') : $t('Continue to PayPal'); - - $(this.submitButtonSelector).html(title); + updateSubmitButton: function (isActive) { + if (this.isPaymentMethodNonceReceived() || !isActive) { + $(this.paypalButtonSelector).html(this.reviewButtonHtml); + } }, /** * @override */ placeOrder: function () { - if (!this.isPaymentMethodNonceReceived()) { - this.payWithPayPal(); - } else { - fullScreenLoader.startLoader(); - - $.when( - setPaymentInformationAction( - this.messageContainer, - this.getData() - ) - ).done(this.done.bind(this)) - .fail(this.fail.bind(this)); - } + fullScreenLoader.startLoader(); + $.when( + setPaymentInformationExtended( + this.messageContainer, + this.getData(), + true + ) + ).done(this.done.bind(this)) + .fail(this.fail.bind(this)); }, /** diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index eaebd8492b0a1..c46e65ffb8abd 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -9,22 +9,28 @@ define([ 'underscore', 'Magento_Checkout/js/view/payment/default', 'Magento_Braintree/js/view/payment/adapter', + 'braintreePayPal', + 'braintreePayPalCheckout', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/model/payment/additional-validators', 'Magento_Vault/js/view/payment/vault-enabler', 'Magento_Checkout/js/action/create-billing-address', + 'Magento_Braintree/js/view/payment/kount', 'mage/translate' ], function ( $, _, Component, - Braintree, + BraintreeAdapter, + BraintreePayPal, + BraintreePayPalCheckout, quote, fullScreenLoader, additionalValidators, VaultEnabler, createBillingAddress, + kount, $t ) { 'use strict'; @@ -34,10 +40,15 @@ define([ template: 'Magento_Braintree/payment/paypal', code: 'braintree_paypal', active: false, - paymentMethodNonce: null, grandTotalAmount: null, isReviewRequired: false, + paypalCheckoutInstance: null, customerEmail: null, + vaultEnabler: null, + paymentPayload: { + nonce: null + }, + paypalButtonSelector: '[data-container="paypal-button"]', /** * Additional payment data @@ -46,39 +57,42 @@ define([ */ additionalData: {}, - /** - * PayPal client configuration - * {Object} - */ - clientConfig: { - dataCollector: { - paypal: true - }, - - /** - * Triggers when widget is loaded - * @param {Object} checkout - */ - onReady: function (checkout) { - Braintree.checkout = checkout; - this.additionalData['device_data'] = checkout.deviceData; - this.enableButton(); - Braintree.onReady(); - }, - - /** - * Triggers on payment nonce receive - * @param {Object} response - */ - onPaymentMethodReceived: function (response) { - this.beforePlaceOrder(response); - } - }, imports: { onActiveChange: 'active' } }, + /** + * Initialize view. + * + * @return {exports} + */ + initialize: function () { + var self = this; + + self._super(); + + BraintreeAdapter.getApiClient().then(function (clientInstance) { + return BraintreePayPal.create({ + client: clientInstance + }); + }).then(function (paypalCheckoutInstance) { + self.paypalCheckoutInstance = paypalCheckoutInstance; + + return self.paypalCheckoutInstance; + }); + + kount.getDeviceData() + .then(function (deviceData) { + self.additionalData['device_data'] = deviceData; + }); + + // for each component initialization need update property + this.isReviewRequired(false); + + return self; + }, + /** * Set list of observable attributes * @returns {exports.initObservable} @@ -109,10 +123,6 @@ define([ } }); - // for each component initialization need update property - this.isReviewRequired(false); - this.initClientConfig(); - return this; }, @@ -161,24 +171,13 @@ define([ }, /** - * Init config - */ - initClientConfig: function () { - this.clientConfig = _.extend(this.clientConfig, this.getPayPalConfig()); - - _.each(this.clientConfig, function (fn, name) { - if (typeof fn === 'function') { - this.clientConfig[name] = fn.bind(this); - } - }, this); - }, - - /** - * Set payment nonce - * @param {String} paymentMethodNonce + * Sets payment payload + * + * @param {Object} paymentPayload + * @private */ - setPaymentMethodNonce: function (paymentMethodNonce) { - this.paymentMethodNonce = paymentMethodNonce; + setPaymentPayload: function (paymentPayload) { + this.paymentPayload = paymentPayload; }, /** @@ -205,21 +204,21 @@ define([ /** * Prepare data to place order - * @param {Object} data + * @param {Object} payload */ - beforePlaceOrder: function (data) { - this.setPaymentMethodNonce(data.nonce); + beforePlaceOrder: function (payload) { + this.setPaymentPayload(payload); if ((this.isRequiredBillingAddress() || quote.billingAddress() === null) && - typeof data.details.billingAddress !== 'undefined' + typeof payload.details.billingAddress !== 'undefined' ) { - this.setBillingAddress(data.details, data.details.billingAddress); + this.setBillingAddress(payload.details, payload.details.billingAddress); } if (this.isSkipOrderReview()) { this.placeOrder(); } else { - this.customerEmail(data.details.email); + this.customerEmail(payload.details.email); this.isReviewRequired(true); } }, @@ -228,18 +227,46 @@ define([ * Re-init PayPal Auth Flow */ reInitPayPal: function () { - if (Braintree.checkout) { - Braintree.checkout.teardown(function () { - Braintree.checkout = null; - }); - } + var self = this; - this.disableButton(); - this.clientConfig.paypal.amount = this.grandTotalAmount; - this.clientConfig.paypal.shippingAddressOverride = this.getShippingAddress(); + $(self.paypalButtonSelector).html(''); + + return BraintreePayPalCheckout.Button.render({ + env: this.getEnvironment(), + style: { + color: 'blue', + shape: 'rect', + size: 'medium', + label: 'pay', + tagline: false + }, + + /** + * Creates a PayPal payment + */ + payment: function () { + return self.paypalCheckoutInstance.createPayment( + self.getPayPalConfig() + ); + }, - Braintree.setConfig(this.clientConfig); - Braintree.setup(); + /** + * Tokenizes the authorize data + */ + onAuthorize: function (data) { + return self.paypalCheckoutInstance.tokenizePayment(data) + .then(function (payload) { + self.beforePlaceOrder(payload); + }); + }, + + /** + * Triggers on error + */ + onError: function () { + self.showError($t('Payment ' + self.getTitle() + ' can\'t be initialized')); + } + }, self.paypalButtonSelector); }, /** @@ -272,37 +299,22 @@ define([ */ getPayPalConfig: function () { var totals = quote.totals(), - config = {}, + config, isActiveVaultEnabler = this.isActiveVault(); - config.paypal = { - container: 'paypal-container', - singleUse: !isActiveVaultEnabler, - headless: true, + config = { + flow: !isActiveVaultEnabler ? 'checkout' : 'vault', amount: this.grandTotalAmount, currency: totals['base_currency_code'], locale: this.getLocale(), enableShippingAddress: true, - - /** - * Triggers on any Braintree error - */ - onError: function () { - this.paymentMethodNonce = null; - }, - - /** - * Triggers if browser doesn't support PayPal Checkout - */ - onUnsupported: function () { - this.paymentMethodNonce = null; - } + shippingAddressEditable: this.isAllowOverrideShippingAddress() }; - config.paypal.shippingAddressOverride = this.getShippingAddress(); + config.shippingAddressOverride = this.getShippingAddress(); if (this.getMerchantName()) { - config.paypal.displayName = this.getMerchantName(); + config.displayName = this.getMerchantName(); } return config; @@ -320,14 +332,13 @@ define([ } return { - recipientName: address.firstname + ' ' + address.lastname, - streetAddress: address.street[0], - locality: address.city, - countryCodeAlpha2: address.countryId, + line1: address.street[0], + city: address.city, + state: address.regionCode, postalCode: address.postcode, - region: address.regionCode, + countryCode: address.countryId, phone: address.telephone, - editable: this.isAllowOverrideShippingAddress() + recipientName: address.firstname + ' ' + address.lastname }; }, @@ -347,7 +358,7 @@ define([ var data = { 'method': this.getCode(), 'additional_data': { - 'payment_method_nonce': this.paymentMethodNonce + 'payment_method_nonce': this.paymentPayload.nonce } }; @@ -374,6 +385,13 @@ define([ return window.checkoutConfig.payment[this.getCode()].vaultCode; }, + /** + * @returns {String} + */ + getEnvironment: function () { + return window.checkoutConfig.payment[BraintreeAdapter.getCode()].environment; + }, + /** * Check if need to skip order review * @returns {Boolean} @@ -394,59 +412,7 @@ define([ * Re-init PayPal Auth flow to use Vault */ onVaultPaymentTokenEnablerChange: function () { - this.clientConfig.paypal.singleUse = !this.isActiveVault(); this.reInitPayPal(); - }, - - /** - * Disable submit button - */ - disableButton: function () { - // stop any previous shown loaders - fullScreenLoader.stopLoader(true); - fullScreenLoader.startLoader(); - $('[data-button="place"]').attr('disabled', 'disabled'); - }, - - /** - * Enable submit button - */ - enableButton: function () { - $('[data-button="place"]').removeAttr('disabled'); - fullScreenLoader.stopLoader(); - }, - - /** - * Triggers when customer click "Continue to PayPal" button - */ - payWithPayPal: function () { - if (!additionalValidators.validate()) { - return; - } - - try { - Braintree.checkout.paypal.initAuthFlow(); - } catch (e) { - this.messageContainer.addErrorMessage({ - message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.') - }); - } - }, - - /** - * Get button title - * @returns {String} - */ - getButtonTitle: function () { - return this.isSkipOrderReview() ? 'Pay with PayPal' : 'Continue to PayPal'; - }, - - /** - * Get button id - * @returns {String} - */ - getButtonId: function () { - return this.getCode() + (this.isSkipOrderReview() ? '_pay_with' : '_continue_to'); } }); }); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/vault.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/vault.js index 85e531706d62e..ad8ac02bfb8c6 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/vault.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/vault.js @@ -51,15 +51,7 @@ define([ placeOrder: function () { var self = this; - /** - * Define onReady callback - */ - Braintree.onReady = function () { - self.getPaymentMethodNonce(); - }; - self.hostedFields(function (formComponent) { - formComponent.initBraintree(); - }); + self.getPaymentMethodNonce(); }, /** @@ -75,7 +67,7 @@ define([ .done(function (response) { fullScreenLoader.stopLoader(); self.hostedFields(function (formComponent) { - formComponent.setPaymentMethodNonce(response.paymentMethodNonce); + formComponent.paymentPayload.nonce = response.paymentMethodNonce; formComponent.additionalData['public_hash'] = self.publicHash; formComponent.code = self.code; formComponent.messageContainer = self.messageContainer; diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/validator-handler.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/validator-handler.js index fbe85c3b46027..992c241fad665 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/validator-handler.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/validator-handler.js @@ -7,28 +7,26 @@ define([ 'jquery', - 'Magento_Ui/js/model/messageList', 'Magento_Braintree/js/view/payment/3d-secure' -], function ($, globalMessageList, verify3DSecure) { +], function ($, verify3DSecure) { 'use strict'; return { + initialized: false, validators: [], /** - * Get payment config - * @returns {Object} - */ - getConfig: function () { - return window.checkoutConfig.payment; - }, - - /** - * Init list of validators + * Inits list of validators */ initialize: function () { var config = this.getConfig(); + if (this.initialized) { + return; + } + + this.initialized = true; + if (config[verify3DSecure.getCode()].enabled) { verify3DSecure.setConfig(config[verify3DSecure.getCode()]); this.add(verify3DSecure); @@ -36,7 +34,17 @@ define([ }, /** - * Add new validator + * Gets payment config + * + * @returns {Object} + */ + getConfig: function () { + return window.checkoutConfig.payment; + }, + + /** + * Adds new validator + * * @param {Object} validator */ add: function (validator) { @@ -44,17 +52,21 @@ define([ }, /** - * Run pull of validators + * Runs pull of validators + * * @param {Object} context - * @param {Function} callback + * @param {Function} successCallback + * @param {Function} errorCallback */ - validate: function (context, callback) { + validate: function (context, successCallback, errorCallback) { var self = this, deferred; + self.initialize(); + // no available validators if (!self.validators.length) { - callback(); + successCallback(); return; } @@ -66,20 +78,10 @@ define([ $.when.apply($, deferred) .done(function () { - callback(); + successCallback(); }).fail(function (error) { - self.showError(error); + errorCallback(error); }); - }, - - /** - * Show error message - * @param {String} errorMessage - */ - showError: function (errorMessage) { - globalMessageList.addErrorMessage({ - message: errorMessage - }); } }; }); diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html index 819b06ca75788..8da8927a3b247 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/form.html @@ -23,7 +23,7 @@ <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> </div> - <form id="co-transparent-form-braintree" class="form" data-bind="" method="post" action="#" novalidate="novalidate"> + <form id="co-transparent-form-braintree" class="form" data-bind="afterRender: initHostedFields" method="post" action="#" novalidate="novalidate"> <fieldset data-bind="attr: {class: 'fieldset payment items ccard ' + getCode(), id: 'payment_form_' + getCode()}"> <legend class="legend"> <span><!-- ko i18n: 'Credit Card Information'--><!-- /ko --></span> diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html index 964e15df166d3..b72ef24b81b63 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html @@ -41,7 +41,7 @@ </div> </div> <div class="field number required"> - <label data-bind="attr: {for: getCode() + '_cc_number'}" class="label"> + <label data-bind="afterRender: initHostedFields, attr: {for: getCode() + '_cc_number'}" class="label"> <span><!-- ko i18n: 'Credit Card Number'--><!-- /ko --></span> </label> <div class="control"> diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html index 722989e41f98f..fcd5320351938 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html @@ -20,6 +20,7 @@ <fieldset class="braintree-paypal-fieldset" data-bind='attr: {id: "payment_form_" + getCode()}'> <div id="paypal-container"></div> </fieldset> + <div data-container="paypal-button"></div> <div class="actions-toolbar braintree-paypal-actions" data-bind="visible: isReviewRequired()"> <div class="payment-method-item braintree-paypal-account"> <span class="payment-method-type">PayPal</span> diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html index e1f6a1b4c25ce..0abf3483ac76c 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html @@ -65,15 +65,7 @@ </div> </div> <div class="actions-toolbar" data-bind="visible: !isReviewRequired()"> - <div class="primary"> - <button data-button="place" data-role="review-save" - type="submit" - data-bind="attr: {id: getButtonId(), title: $t(getButtonTitle())}, enable: (isActive()), click: payWithPayPal" - class="action primary checkout" - disabled> - <span translate="getButtonTitle()"></span> - </button> - </div> + <div data-container="paypal-button" class="primary"></div> </div> </div> </div> diff --git a/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php b/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php new file mode 100644 index 0000000000000..23ca1d88e3625 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BraintreeGraphQl\Model; + +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderInterface; + +/** + * Format Braintree input into value expected when setting payment method + */ +class BraintreeDataProvider implements AdditionalDataProviderInterface +{ + private const PATH_ADDITIONAL_DATA = 'braintree'; + + /** + * Format Braintree input into value expected when setting payment method + * + * @param array $args + * @return array + * @throws GraphQlInputException + */ + public function getData(array $args): array + { + if (!isset($args[self::PATH_ADDITIONAL_DATA])) { + throw new GraphQlInputException( + __('Required parameter "braintree" for "payment_method" is missing.') + ); + } + + if (!isset($args[self::PATH_ADDITIONAL_DATA]['payment_method_nonce'])) { + throw new GraphQlInputException( + __('Required parameter "payment_method_nonce" for "braintree" is missing.') + ); + } + + if (!isset($args[self::PATH_ADDITIONAL_DATA]['is_active_payment_token_enabler'])) { + throw new GraphQlInputException( + __('Required parameter "is_active_payment_token_enabler" for "braintree" is missing.') + ); + } + + return $args[self::PATH_ADDITIONAL_DATA]; + } +} diff --git a/app/code/Magento/BraintreeGraphQl/Model/BraintreeVaultDataProvider.php b/app/code/Magento/BraintreeGraphQl/Model/BraintreeVaultDataProvider.php new file mode 100644 index 0000000000000..4635e4e317bc1 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Model/BraintreeVaultDataProvider.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BraintreeGraphQl\Model; + +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderInterface; + +/** + * Format Braintree input into value expected when setting payment method + */ +class BraintreeVaultDataProvider implements AdditionalDataProviderInterface +{ + private const PATH_ADDITIONAL_DATA = 'braintree_cc_vault'; + + /** + * Format Braintree input into value expected when setting payment method + * + * @param array $args + * @return array + */ + public function getData(array $args): array + { + if (!isset($args[self::PATH_ADDITIONAL_DATA])) { + throw new GraphQlInputException( + __('Required parameter "braintree_cc_vault" for "payment_method" is missing.') + ); + } + + if (!isset($args[self::PATH_ADDITIONAL_DATA]['public_hash'])) { + throw new GraphQlInputException( + __('Required parameter "public_hash" for "braintree_cc_vault" is missing.') + ); + } + + return $args[self::PATH_ADDITIONAL_DATA]; + } +} diff --git a/app/code/Magento/BraintreeGraphQl/Model/Resolver/CreateBraintreeClientToken.php b/app/code/Magento/BraintreeGraphQl/Model/Resolver/CreateBraintreeClientToken.php new file mode 100644 index 0000000000000..82c1196f0e689 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Model/Resolver/CreateBraintreeClientToken.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BraintreeGraphQl\Model\Resolver; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\Request\PaymentDataBuilder; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Resolver for generating Braintree client token + */ +class CreateBraintreeClientToken implements ResolverInterface +{ + /** + * @var Config + */ + private $config; + + /** + * @var BraintreeAdapterFactory + */ + private $adapterFactory; + + /** + * @param Config $config + * @param BraintreeAdapterFactory $adapterFactory + */ + public function __construct( + Config $config, + BraintreeAdapterFactory $adapterFactory + ) { + $this->config = $config; + $this->adapterFactory = $adapterFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + + if (!$this->config->isActive($storeId)) { + throw new GraphQlInputException(__('The Braintree payment method is not active.')); + } + + $params = []; + $merchantAccountId = $this->config->getMerchantAccountId($storeId); + if (!empty($merchantAccountId)) { + $params[PaymentDataBuilder::MERCHANT_ACCOUNT_ID] = $merchantAccountId; + } + + return $this->adapterFactory->create($storeId)->generate($params); + } +} diff --git a/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php b/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php new file mode 100644 index 0000000000000..1dea9992c6306 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BraintreeGraphQl\Plugin; + +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Psr\Log\LoggerInterface; + +/** + * Plugin creating nonce from Magento Vault Braintree public hash + */ +class SetVaultPaymentNonce +{ + /** + * @var GetPaymentNonceCommand + */ + private $command; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param GetPaymentNonceCommand $command + * @param LoggerInterface $logger + */ + public function __construct( + GetPaymentNonceCommand $command, + LoggerInterface $logger + ) { + $this->command = $command; + $this->logger = $logger; + } + + /** + * Set Braintree nonce from public hash + * + * @param \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject + * @param \Magento\Quote\Model\Quote $quote + * @param array $paymentData + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeExecute( + \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject, + \Magento\Quote\Model\Quote $quote, + array $paymentData + ): array { + if ($paymentData['code'] !== ConfigProvider::CC_VAULT_CODE + || !isset($paymentData[ConfigProvider::CC_VAULT_CODE]) + || !isset($paymentData[ConfigProvider::CC_VAULT_CODE]['public_hash']) + ) { + return [$quote, $paymentData]; + } + + $subject = [ + 'public_hash' => $paymentData[ConfigProvider::CC_VAULT_CODE]['public_hash'], + 'customer_id' => $quote->getCustomerId(), + 'store_id' => $quote->getStoreId(), + ]; + + try { + $result = $this->command->execute($subject)->get(); + $paymentData[ConfigProvider::CC_VAULT_CODE]['payment_method_nonce'] = $result['paymentMethodNonce']; + } catch (\Exception $e) { + $this->logger->critical($e); + throw new GraphQlInputException(__('Sorry, but something went wrong')); + } + + return [$quote, $paymentData]; + } +} diff --git a/app/code/Magento/BraintreeGraphQl/README.md b/app/code/Magento/BraintreeGraphQl/README.md new file mode 100644 index 0000000000000..f6740e4d250e9 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/README.md @@ -0,0 +1,4 @@ +# BraintreeGraphQl + +**BraintreeGraphQl** provides type and resolver for method additional +information. \ No newline at end of file diff --git a/app/code/Magento/BraintreeGraphQl/composer.json b/app/code/Magento/BraintreeGraphQl/composer.json new file mode 100644 index 0000000000000..e913d51e238a3 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/composer.json @@ -0,0 +1,29 @@ +{ + "name": "magento/module-braintree-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "102.0.*", + "magento/module-braintree": "100.3.*", + "magento/module-store": "101.0.*", + "magento/module-quote": "101.1.*", + "magento/module-quote-graph-ql": "100.3.*" + }, + "suggest": { + "magento/module-graph-ql": "100.3.*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\BraintreeGraphQl\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml b/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..a310663163771 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderPool"> + <arguments> + <argument name="dataProviders" xsi:type="array"> + <item name="braintree" xsi:type="object">Magento\BraintreeGraphQl\Model\BraintreeDataProvider</item> + <item name="braintree_cc_vault" xsi:type="object">Magento\BraintreeGraphQl\Model\BraintreeVaultDataProvider</item> + </argument> + </arguments> + </type> + <type name="Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart"> + <plugin name="braintree_generate_vault_nonce" type="Magento\BraintreeGraphQl\Plugin\SetVaultPaymentNonce" /> + </type> +</config> diff --git a/app/code/Magento/BraintreeGraphQl/etc/module.xml b/app/code/Magento/BraintreeGraphQl/etc/module.xml new file mode 100644 index 0000000000000..2133e95a69104 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_BraintreeGraphQl"/> +</config> diff --git a/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls b/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..0492f8aaf989b --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls @@ -0,0 +1,22 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Mutation { + createBraintreeClientToken: String! @resolver(class: "\\Magento\\BraintreeGraphQl\\Model\\Resolver\\CreateBraintreeClientToken") @doc(description:"Creates Braintree Client Token for creating client-side nonce.") +} + +input PaymentMethodInput { + braintree: BraintreeInput + braintree_cc_vault: BraintreeCcVaultInput +} + +input BraintreeInput { + payment_method_nonce: String! + is_active_payment_token_enabler: Boolean! + device_data: String +} + +input BraintreeCcVaultInput { + public_hash: String! + device_data: String +} diff --git a/app/code/Magento/BraintreeGraphQl/registration.php b/app/code/Magento/BraintreeGraphQl/registration.php new file mode 100644 index 0000000000000..37f7ef30864cb --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/registration.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_BraintreeGraphQl', __DIR__); diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php index a768e2450bfe8..91b015782fe1f 100644 --- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php +++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php @@ -7,7 +7,7 @@ namespace Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Attributes; /** - * Bundle Extended Attribures Block. + * Bundle Extended Attributes Block. * * @author Magento Core Team <core@magentocommerce.com> */ diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php index b5dfd312cd0c4..077ebd4422aab 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php @@ -128,7 +128,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @param array $dimensions * @param \Traversable $entityIds * @throws \Exception @@ -137,18 +137,20 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds) { $this->tableMaintainer->createMainTmpTable($dimensions); - $temporaryPriceTable = $this->indexTableStructureFactory->create([ - 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), - 'entityField' => 'entity_id', - 'customerGroupField' => 'customer_group_id', - 'websiteField' => 'website_id', - 'taxClassField' => 'tax_class_id', - 'originalPriceField' => 'price', - 'finalPriceField' => 'final_price', - 'minPriceField' => 'min_price', - 'maxPriceField' => 'max_price', - 'tierPriceField' => 'tier_price', - ]); + $temporaryPriceTable = $this->indexTableStructureFactory->create( + [ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ] + ); $entityIds = iterator_to_array($entityIds); @@ -331,11 +333,13 @@ private function prepareBundlePriceByType($priceType, array $dimensions, $entity 'ROUND((1 - ' . $tierExpr . ' / 100) * ' . $price . ', 4)', 'NULL' ); - $finalPrice = $connection->getLeastSql([ - $price, - $connection->getIfNullSql($specialPriceExpr, $price), - $connection->getIfNullSql($tierPrice, $price), - ]); + $finalPrice = $connection->getLeastSql( + [ + $price, + $connection->getIfNullSql($specialPriceExpr, $price), + $connection->getIfNullSql($tierPrice, $price), + ] + ); } else { $finalPrice = new \Zend_Db_Expr('0'); $tierPrice = $connection->getCheckSql($tierExpr . ' IS NOT NULL', '0', 'NULL'); @@ -471,10 +475,12 @@ private function calculateBundleSelectionPrice($dimensions, $priceType) 'NULL' ); - $priceExpr = $connection->getLeastSql([ - $priceExpr, - $connection->getIfNullSql($tierExpr, $priceExpr), - ]); + $priceExpr = $connection->getLeastSql( + [ + $priceExpr, + $connection->getIfNullSql($tierExpr, $priceExpr), + ] + ); } else { $price = 'idx.min_price * bs.selection_qty'; $specialExpr = $connection->getCheckSql( @@ -487,10 +493,12 @@ private function calculateBundleSelectionPrice($dimensions, $priceType) 'ROUND((1 - i.tier_percent / 100) * ' . $price . ', 4)', 'NULL' ); - $priceExpr = $connection->getLeastSql([ - $specialExpr, - $connection->getIfNullSql($tierExpr, $price), - ]); + $priceExpr = $connection->getLeastSql( + [ + $specialExpr, + $connection->getIfNullSql($tierExpr, $price), + ] + ); } $metadata = $this->metadataPool->getMetadata(ProductInterface::class); @@ -613,7 +621,7 @@ private function prepareTierPriceIndex($dimensions, $entityIds) * Create bundle price. * * @param IndexTableStructure $priceTable - * @return void + * @return void */ private function applyBundlePrice($priceTable): void { @@ -699,7 +707,7 @@ private function getMainTable($dimensions) /** * Get connection * - * return \Magento\Framework\DB\Adapter\AdapterInterface + * @return \Magento\Framework\DB\Adapter\AdapterInterface * @throws \DomainException */ private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index 5b88288ff72ca..7b3f6dd8bbefa 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -15,6 +15,7 @@ * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection @@ -145,7 +146,8 @@ protected function _construct() } /** - * Set store id for each collection item when collection was loaded + * Set store id for each collection item when collection was loaded. + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod * * @return $this */ diff --git a/app/code/Magento/Bundle/Setup/Patch/Data/ApplyAttributesUpdate.php b/app/code/Magento/Bundle/Setup/Patch/Data/ApplyAttributesUpdate.php index d8ad1757ab2e6..cdbe1906df843 100644 --- a/app/code/Magento/Bundle/Setup/Patch/Data/ApplyAttributesUpdate.php +++ b/app/code/Magento/Bundle/Setup/Patch/Data/ApplyAttributesUpdate.php @@ -14,8 +14,7 @@ use Magento\Eav\Setup\EavSetupFactory; /** - * Class ApplyAttributesUpdate - * @package Magento\Bundle\Setup\Patch + * Class \Magento\Bundle\Setup\Patch\ApplyAttributesUpdate */ class ApplyAttributesUpdate implements DataPatchInterface, PatchVersionInterface { @@ -44,7 +43,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function apply() @@ -66,8 +65,8 @@ public function apply() ',', $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field, 'apply_to') ); - if (!in_array('bundle', $applyTo)) { - $applyTo[] = 'bundle'; + if (!in_array(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE, $applyTo)) { + $applyTo[] = \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE; $eavSetup->updateAttribute( \Magento\Catalog\Model\Product::ENTITY, $field, @@ -78,7 +77,7 @@ public function apply() } $applyTo = explode(',', $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'cost', 'apply_to')); - unset($applyTo[array_search('bundle', $applyTo)]); + unset($applyTo[array_search(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE, $applyTo)]); $eavSetup->updateAttribute(\Magento\Catalog\Model\Product::ENTITY, 'cost', 'apply_to', implode(',', $applyTo)); /** @@ -106,7 +105,7 @@ public function apply() 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => 'bundle' + 'apply_to' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE ] ); @@ -131,7 +130,7 @@ public function apply() 'comparable' => false, 'visible_on_front' => false, 'unique' => false, - 'apply_to' => 'bundle' + 'apply_to' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE ] ); @@ -157,7 +156,7 @@ public function apply() 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => 'bundle' + 'apply_to' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE ] ); @@ -184,7 +183,7 @@ public function apply() 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => 'bundle' + 'apply_to' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE ] ); @@ -210,13 +209,13 @@ public function apply() 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => 'bundle' + 'apply_to' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE ] ); } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -224,7 +223,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -232,7 +231,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminOrderBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminOrderBundleProductActionGroup.xml new file mode 100644 index 0000000000000..d73d31c4498f8 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminOrderBundleProductActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOrderConfigureBundleProduct"> + <arguments> + <argument name="productName" type="string" defaultValue="{{SimpleProduct.sku}}"/> + <argument name="productNumber" type="string" defaultValue="1"/> + <argument name="productQty" type="string" defaultValue="1"/> + </arguments> + <click selector="{{AdminOrderFormItemsOrderedSection.configureButtonBySku}}" stepKey="clickConfigure"/> + <waitForPageLoad stepKey="waitForConfigurePageLoad"/> + <checkOption selector="{{AdminOrderBundleProductSection.bundleProductCheckbox(productNumber)}}" stepKey="checkProduct"/> + <fillField selector="{{AdminOrderFormConfigureProductSection.quantity}}" userInput="{{productQty}}" stepKey="fillProductQty"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOk"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml new file mode 100644 index 0000000000000..e6afdce6ab697 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectBundleProductDropDownOptionActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection(productName)}}" stepKey="selectProduct"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml new file mode 100644 index 0000000000000..cf2ccfac47023 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup"> + <waitForElementVisible selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="waitForCustomizeAndAddToCartButton"/> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickOnCustomizeAndAddToCartButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index 0a0c77755fc7a..6e7e4a7a16573 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -91,4 +91,51 @@ <requiredEntity type="custom_attribute">CustomAttributeFixPrice</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributePriceView</requiredEntity> </entity> + <entity name="BundleProductPriceViewRange" type="product2"> + <data key="name" unique="suffix">BundleProduct</data> + <data key="sku" unique="suffix">bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">bundle-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductShortDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeDynamicPrice</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributePriceViewRange</requiredEntity> + </entity> + <entity name="DynamicBundleProductCustomDescription" type="product2"> + <data key="name" unique="suffix">Test 123 Dynamic </data> + <data key="sku" unique="suffix">test-dynamic-bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">test-dynamic-bundle-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomDynamicProductDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomDynamicProductShortDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeDynamicPrice</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributePriceViewRange</requiredEntity> + </entity> + <entity name="FixedBundleProductCustomDescription" type="product2"> + <data key="name" unique="suffix">Test 123 Fixed </data> + <data key="sku" unique="suffix">test-fixed-bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="price">10</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">api-fixed-bundle-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomFixedProductDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomFixedProductShortDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributePriceView</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeFixPrice</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeFixWeight</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeFixSku</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminOrderBundleProductSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminOrderBundleProductSection.xml new file mode 100644 index 0000000000000..915b11ebdbbaa --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminOrderBundleProductSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> +<section name="AdminOrderBundleProductSection"> + <element name="bundleProductCheckbox" type="checkbox" selector="(//input[contains(@class, 'admin__control-checkbox') and contains(@class, 'bundle-option')])[{{productNumber}}]" parameterized="true"/> +</section> +</sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index 516f40ac2e7b7..bd13f4daa0dbd 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -41,8 +41,9 @@ <element name="contentDropDownIfNotShowing" type="button" selector="//div[@data-index='content']//div[contains(@class, '_hide')]"/> <element name="longDescription" type="input" selector="#product_form_description"/> <element name="shortDescription" type="input" selector="#product_form_short_description"/> - <!--BundleOptinsDropDown--> + <!--BundleOptionsDropDown--> <element name="bundleOptionsDropDown" type="button" selector="div[data-index='bundle-items']" timeout="30"/> + <element name="currentBundleOption" type="text" selector="//div[@data-index='bundle-items']//div[contains(@class, 'admin__collapsible-title')]/span"/> <!--AddingAnOption--> <element name="addOptions" type="button" selector="//tr[@data-repeat-index='0']//td[4]" timeout="30"/> <!--SEODropdownTab--> @@ -92,7 +93,7 @@ <!--Category Selection--> <element name="categoryByName" type="multiselect" selector="//div[@data-index='category_ids']//span[contains(text(), '{{category}}')]" parameterized="true"/> <element name="searchForCategory" type="input" selector="div.action-menu._active > div.admin__action-multiselect-search-wrap input" timeout="30"/> - <element name="selectCategory" type="multiselect" selector="//div[@class='action-menu _active']//label[@class='admin__action-multiselect-label']"/> + <element name="selectCategory" type="multiselect" selector="//div[@class='action-menu _active']//label[@class='admin__action-multiselect-label']" timeout="30"/> <element name="categoriesLabel" type="text" selector="//div[@class='action-menu _active']//button[@data-action='close-advanced-select']"/> <element name="userDefinedQuantity" type="checkbox" selector="[name='bundle_options[bundle_options][{{option}}][bundle_selections][{{product}}][selection_can_change_qty]'][type='checkbox']" parameterized="true"/> </section> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index 30a7e8b777f3b..c47cf6095c777 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -37,5 +37,6 @@ <element name="multiselectOptionFourProducts" type="multiselect" selector="//label//span[contains(text(), '{{productName}}')]/../..//select[@multiple='multiple']" parameterized="true"/> <element name="currencyTrigger" type="select" selector="#switcher-currency-trigger" timeout="30"/> <element name="currency" type="select" selector="//a[text()='{{arg}}']" parameterized="true"/> + <element name="multiSelectOption" type="select" selector="//div[@class='field option required']//select"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml index 9dc4aed26bef0..eb92fd3756497 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -8,8 +8,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBundleProductActionSection"> - <element name="customizeAndAddToCartButton" type="button" selector="#bundle-slide"/> + <element name="customizeAndAddToCartButton" type="button" selector="#bundle-slide" timeout="30"/> <element name="quantityField" type="input" selector="#qty"/> - <element name="addToCartButton" type="button" selector="#product-addtocart-button"/> + <element name="addToCartButton" type="button" selector="#product-addtocart-button" timeout="30"/> + <element name="dropdownSelectOption" type="select" selector="//div[@class='control']/select"/> + <element name="dropdownProductSelection" type="select" selector="//div[@class='control']/select/option[contains(.,'{{productName}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml index bc9a3dba9a5f1..a4e26256e9773 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml @@ -16,6 +16,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-11016"/> <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16393"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml index f87897bd579a3..1f46e1fc9f0b1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml @@ -31,6 +31,8 @@ <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/> + <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteAllProducts"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- go to bundle product creation page--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml new file mode 100644 index 0000000000000..fe55fda4d0e05 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest"> + <annotations> + <features value="Bundle"/> + <stories value="Admin list bundle products"/> + <title value="Admin should be able to mass update attributes for bundle products"/> + <description value="Admin should be able to mass update attributes for bundle products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-219"/> + <group value="bundle"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <!-- Create Simple Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <!-- Create Fixed Bundle Product --> + <createData entity="ApiFixedBundleProduct" stepKey="createFixedBundleProduct"/> + <!-- Create DropDown Bundle Option --> + <createData entity="DropDownBundleOption" stepKey="createBundleOption"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + </createData> + <!-- Link Simple Product --> + <createData entity="ApiBundleLink" stepKey="createNewBundleLink"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + <requiredEntity createDataKey="createBundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + </before> + <after> + <!-- Delete Simple Product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <!-- Delete Fixed Bundle Product --> + <deleteData createDataKey="createFixedBundleProduct" stepKey="deleteBundleProduct"/> + <!-- Clear Filter --> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductFilter"/> + <!--Log Out Admin--> + <actionGroup ref="logout" stepKey="logoutAsAdmin"/> + </after> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Go to Catalog -> Catalog -> Products and Search created product in precondition and choose it --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> + <argument name="product" value="$$createFixedBundleProduct$$"/> + </actionGroup> + <!-- Choose "Update attributes" and Change any product data --> + <actionGroup ref="AdminUpdateProductNameAndDescriptionAttributes" stepKey="updateProductAttribute"> + <argument name="product" value="UpdateAttributeNameAndDescription"/> + </actionGroup> + <!--Run cron twice--> + <magentoCLI command="cron:run" stepKey="cronRun"/> + <magentoCLI command="cron:run" stepKey="cronRunTwice"/> + <!-- Search for a product with a new name and Open Product --> + <actionGroup ref="filterProductGridByName" stepKey="searchWithNewProductName"> + <argument name="product" value="UpdateAttributeNameAndDescription"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProductPage"> + <argument name="product" value="$$createFixedBundleProduct$$"/> + </actionGroup> + <!-- Assert product name and description --> + <actionGroup ref="AssertProductNameInProductEditForm" stepKey="assertProductName"> + <argument name="productName" value="{{UpdateAttributeNameAndDescription.name}}"/> + </actionGroup> + <actionGroup ref="AssertProductDescriptionInProductEditForm" stepKey="assertProductDescription"> + <argument name="productDescription" value="{{UpdateAttributeNameAndDescription.description}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml index a1630128638d9..779f1370c4da4 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MAGETWO-95933"/> <group value="Bundle"/> + <skip> + <issueId value="MC-16684"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="login"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml index 285503465a011..d7394b2dbf32e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml @@ -32,6 +32,8 @@ <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/> + <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteAllProducts"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- go to bundle product creation page--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml index 4c39cbc4ab0a4..532af1ea76dca 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml @@ -13,8 +13,10 @@ <features value="Bundle"/> <stories value="View bundle products"/> <title value="Check tier prices for bundle options"/> + <description value="Check tier prices for bundle options"/> <testCaseId value="MAGETWO-98968"/> <useCaseId value="MAGETWO-98603"/> + <severity value="AVERAGE"/> <group value="catalog"/> <group value="bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml new file mode 100644 index 0000000000000..d27cd0df88239 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerSearchBundleProductsByKeywordsTest"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle products list on Storefront"/> + <title value="Customer should be able to see search results when searching for bundle products by keyword"/> + <description value="Customer should be able to see search results when searching for bundle products by keyword"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-227"/> + <group value="bundle"/> + </annotations> + <before> + <createData entity="SimpleProductNotVisibleIndividually" stepKey="createSimpleProduct"/> + <createData entity="DynamicBundleProductCustomDescription" stepKey="createDynamicBundle"/> + <createData entity="DropDownBundleOption" stepKey="dynamicBundleOption"> + <requiredEntity createDataKey="createDynamicBundle"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createDynamicBundleLink"> + <requiredEntity createDataKey="createDynamicBundle"/> + <requiredEntity createDataKey="dynamicBundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <createData entity="SimpleProductNotVisibleIndividually" stepKey="createSimpleProductTwo"/> + <createData entity="FixedBundleProductCustomDescription" stepKey="createFixedBundle"/> + <createData entity="DropDownBundleOption" stepKey="fixedBundleOption"> + <requiredEntity createDataKey="createFixedBundle"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createFixedBundleLink"> + <requiredEntity createDataKey="createFixedBundle"/> + <requiredEntity createDataKey="fixedBundleOption"/> + <requiredEntity createDataKey="createSimpleProductTwo"/> + </createData> + <magentoCLI command="indexer:reindex" arguments="cataloginventory_stock catalogsearch_fulltext" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createDynamicBundle" stepKey="deleteDynamicBundleProduct"/> + <deleteData createDataKey="createFixedBundle" stepKey="deleteFixedBundleProduct"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createSimpleProductTwo" stepKey="createSimpleProductTwo"/> + </after> + <!-- 1. Go to storefront home page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <!-- 2. Fill quick search bar with test values unique for dynamic bundle product and click search --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchDynamic"> + <argument name="phrase" value="Dynamic"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="assertDynamicBundleInSearchResultByDynamic"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="assertFixedBundleInSearchResultByDynamic"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDescription"> + <argument name="phrase" value="Dynamicscription"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="assertDynamicBundleInSearchResultByDescription"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeFixedBundleInSearchResultByDescription"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchShortDescription"> + <argument name="phrase" value="Dynamictest"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="assertDynamicBundleInSearchResultByShortDescription"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeFixedBundleInSearchResultByShortDescription"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <!-- 3. Fill quick search bar with test values mutual for both products and click search --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchTest123"> + <argument name="phrase" value="Test 123"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeDynamicBundleByTest123"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByTest123"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchTesting321"> + <argument name="phrase" value="Testing 321"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeDynamicBundleByTesting321"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByTesting321"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchShort555"> + <argument name="phrase" value="Short 555"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeDynamicBundleByShort555"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByShort555"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <!-- 4. Fill quick search bar with test values unique for fixed bundle product and click search --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByFixed"> + <argument name="phrase" value="Fixed"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByFixed"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeDynamicBundleByFixed"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDescriptionForFixed"> + <argument name="phrase" value="Fixedscription"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByDescriptionForFixed"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeDynamicProductByDescriptionForFixed"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByShortDescriptionForFixed"> + <argument name="phrase" value="Fixedtest"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchSeeProductByName" stepKey="seeFixedBundleByShortDescriptionForFixed"> + <argument name="productName" value="$createFixedBundle.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeDynamicBundleByShortDescriptionForFixed"> + <argument name="productName" value="$createDynamicBundle.name$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index 02cafaf3eeb2b..f913acac6066e 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -41,5 +41,5 @@ "Magento\\Bundle\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Bundle/etc/db_schema.xml b/app/code/Magento/Bundle/etc/db_schema.xml index 33738cd252d61..ade8fbf7cf1ce 100644 --- a/app/code/Magento/Bundle/etc/db_schema.xml +++ b/app/code/Magento/Bundle/etc/db_schema.xml @@ -97,7 +97,7 @@ comment="Website Id"/> <column xsi:type="smallint" name="selection_price_type" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Selection Price Type"/> - <column xsi:type="decimal" name="selection_price_value" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="selection_price_value" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Selection Price Value"/> <column xsi:type="int" name="parent_product_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent Product Id"/> @@ -125,9 +125,9 @@ comment="Website Id"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Customer Group ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="false" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="false" comment="Max Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -181,21 +181,21 @@ default="0" comment="Tax Class ID"/> <column xsi:type="smallint" name="price_type" padding="5" unsigned="true" nullable="false" identity="false" comment="Price Type"/> - <column xsi:type="decimal" name="special_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="special_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Special Price"/> - <column xsi:type="decimal" name="tier_percent" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_percent" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Percent"/> - <column xsi:type="decimal" name="orig_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="orig_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Orig Price"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="base_tier" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tier" scale="6" precision="20" unsigned="false" nullable="true" comment="Base Tier"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -203,7 +203,7 @@ <column name="website_id"/> </constraint> </table> - <table name="catalog_product_index_price_bundle_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_bundle_tmp" resource="default" engine="innodb" comment="Catalog Product Index Price Bundle Tmp"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -215,21 +215,21 @@ default="0" comment="Tax Class ID"/> <column xsi:type="smallint" name="price_type" padding="5" unsigned="true" nullable="false" identity="false" comment="Price Type"/> - <column xsi:type="decimal" name="special_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="special_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Special Price"/> - <column xsi:type="decimal" name="tier_percent" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_percent" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Percent"/> - <column xsi:type="decimal" name="orig_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="orig_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Orig Price"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="base_tier" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tier" scale="6" precision="20" unsigned="false" nullable="true" comment="Base Tier"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -253,9 +253,9 @@ default="0" comment="Group Type"/> <column xsi:type="smallint" name="is_required" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Is Required"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -265,7 +265,7 @@ <column name="selection_id"/> </constraint> </table> - <table name="catalog_product_index_price_bundle_sel_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_bundle_sel_tmp" resource="default" engine="innodb" comment="Catalog Product Index Price Bundle Sel Tmp"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -281,9 +281,9 @@ default="0" comment="Group Type"/> <column xsi:type="smallint" name="is_required" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Is Required"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -303,15 +303,15 @@ comment="Website ID"/> <column xsi:type="int" name="option_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Option Id"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="alt_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="alt_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Alt Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="alt_tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="alt_tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Alt Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -320,7 +320,7 @@ <column name="option_id"/> </constraint> </table> - <table name="catalog_product_index_price_bundle_opt_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_bundle_opt_tmp" resource="default" engine="innodb" comment="Catalog Product Index Price Bundle Opt Tmp"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -330,15 +330,15 @@ comment="Website ID"/> <column xsi:type="int" name="option_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Option Id"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="alt_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="alt_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Alt Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="alt_tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="alt_tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Alt Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml index a770ae864a74c..f028c7013df90 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Attributes\Extend */ $elementHtml = $block->getParentElementHtml(); @@ -20,8 +18,8 @@ $isElementReadonly = $block->getElement() ->getReadonly(); ?> -<?php if (!($attributeCode === 'price' && $block->getCanReadPrice() === false)): ?> - <div class="<?= /* @escapeNotVerified */ $attributeCode ?> "><?= /* @escapeNotVerified */ $elementHtml ?></div> +<?php if (!($attributeCode === 'price' && $block->getCanReadPrice() === false)) : ?> + <div class="<?= $block->escapeHtmlAttr($attributeCode) ?> "><?= /* @noEscape */ $elementHtml ?></div> <?php endif; ?> <?= $block->getExtendedElement($switchAttributeCode)->toHtml() ?> @@ -29,9 +27,9 @@ $isElementReadonly = $block->getElement() <?php if (!$isElementReadonly && $block->getDisableChild()) { ?> <script> require(['prototype'], function () { - function <?= /* @escapeNotVerified */ $switchAttributeCode ?>_change() { - var $attribute = $('<?= /* @escapeNotVerified */ $attributeCode ?>'); - if ($('<?= /* @escapeNotVerified */ $switchAttributeCode ?>').value == '<?= /* @escapeNotVerified */ $block::DYNAMIC ?>') { + function <?= /* @noEscape */ $switchAttributeCode ?>_change() { + var $attribute = $('<?= $block->escapeJs($attributeCode) ?>'); + if ($('<?= /* @noEscape */ $switchAttributeCode ?>').value == '<?= $block->escapeJs($block::DYNAMIC) ?>') { if ($attribute) { $attribute.disabled = true; $attribute.value = ''; @@ -43,10 +41,10 @@ $isElementReadonly = $block->getElement() } else { if ($attribute) { <?php if ($attributeCode === 'price' && !$block->getCanEditPrice() && $block->getCanReadPrice() - && $block->getProduct()->isObjectNew()): ?> - <?php $defaultProductPrice = $block->getDefaultProductPrice() ?: "''"; ?> - $attribute.value = <?= /* @escapeNotVerified */ $defaultProductPrice ?>; - <?php else: ?> + && $block->getProduct()->isObjectNew()) : ?> + <?php $defaultProductPrice = $block->getDefaultProductPrice() ?: "''"; ?> + $attribute.value = <?= /* @noEscape */ (string)$defaultProductPrice ?>; + <?php else : ?> $attribute.disabled = false; $attribute.addClassName('required-entry'); <?php endif; ?> @@ -58,11 +56,11 @@ $isElementReadonly = $block->getElement() } <?php if (!($attributeCode === 'price' && !$block->getCanEditPrice() - && !$block->getProduct()->isObjectNew())): ?> - $('<?= /* @escapeNotVerified */ $switchAttributeCode ?>').observe('change', <?= /* @escapeNotVerified */ $switchAttributeCode ?>_change); + && !$block->getProduct()->isObjectNew())) : ?> + $('<?= /* @noEscape */ $switchAttributeCode ?>').observe('change', <?= /* @noEscape */ $switchAttributeCode ?>_change); <?php endif; ?> Event.observe(window, 'load', function(){ - <?= /* @escapeNotVerified */ $switchAttributeCode ?>_change(); + <?= /* @noEscape */ $switchAttributeCode ?>_change(); }); }); </script> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml index 87798a6ba622f..53ad0a963244d 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml @@ -3,17 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Bundle */ ?> <?php $options = $block->decorateArray($block->getOptions(true)); ?> -<?php if (count($options)): ?> +<?php if (count($options)) : ?> <fieldset id="catalog_product_composite_configure_fields_bundle" class="fieldset admin__fieldset composite-bundle<?= $block->getIsLastFieldset() ? ' last-fieldset' : '' ?>"> - <legend class="legend admin__legend"><span><?= /* @escapeNotVerified */ __('Bundle Items') ?></span></legend><br /> + <legend class="legend admin__legend"> + <span><?= $block->escapeHtml(__('Bundle Items')) ?></span> + </legend><br /> <?php foreach ($options as $option) : ?> <?php if ($option->getSelections()) : ?> <?= $block->getOptionHtml($option) ?> @@ -71,7 +70,7 @@ require([ } } }; - ProductConfigure.bundleControl = new BundleControl(<?= /* @escapeNotVerified */ $block->getJsonConfig() ?>); + ProductConfigure.bundleControl = new BundleControl(<?= /* @noEscape */ $block->getJsonConfig() ?>); }); </script> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml index 44ed02f2758d0..08e89699b1f71 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/checkbox.phtml @@ -3,60 +3,58 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Options\Type\Checkbox */ ?> <?php $_option = $block->getOption(); ?> <?php $_selections = $_option->getSelections(); ?> -<?php $_skipSaleableCheck = $this->helper('Magento\Catalog\Helper\Product')->getSkipSaleableCheck(); ?> +<?php $_skipSaleableCheck = $this->helper(Magento\Catalog\Helper\Product::class)->getSkipSaleableCheck(); ?> -<div class="field admin__field options<?php if ($_option->getRequired()) echo ' required _required' ?>"> +<div class="field admin__field options<?php if ($_option->getRequired()) { echo ' required _required'; } ?>"> <label class="label admin__field-label"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> </label> <div class="control admin__field-control"> - <div class="nested <?php if ($_option->getDecoratedIsLast()):?> last<?php endif;?>"> + <div class="nested <?php if ($_option->getDecoratedIsLast()) :?> last<?php endif;?>"> - <?php if (count($_selections) == 1 && $_option->getRequired()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> + <?php if (count($_selections) == 1 && $_option->getRequired()) : ?> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> <input type="hidden" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selections[0]) ?>" /> - <?php else:?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>" + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selections[0])) ?>" /> + <?php else :?> - <?php foreach ($_selections as $_selection): ?> + <?php foreach ($_selections as $_selection) : ?> <div class="field choice admin__field admin__field-option"> <input - class="change-container-classname admin__control-checkbox checkbox bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> <?php if ($_option->getRequired()) echo 'validate-one-required-by-name' ?>" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + class="change-container-classname admin__control-checkbox checkbox bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> <?php if ($_option->getRequired()) { echo 'validate-one-required-by-name'; } ?>" + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" type="checkbox" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][<?= /* @escapeNotVerified */ $_selection->getId() ?>]" - <?php if ($block->isSelected($_selection)):?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][<?= $block->escapeHtmlAttr($_selection->getId()) ?>]" + <?php if ($block->isSelected($_selection)) :?> <?= ' checked="checked"' ?> <?php endif;?> - <?php if (!$_selection->isSaleable() && !$_skipSaleableCheck):?> + <?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) :?> <?= ' disabled="disabled"' ?> <?php endif;?> - value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" onclick="ProductConfigure.bundleControl.changeSelection(this)" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selection) ?>" /> + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selection)) ?>" /> <label class="admin__field-label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"> - <span><?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection) ?></span> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"> + <span><?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selection) ?></span> </label> - <?php if ($_option->getRequired()): ?> - <?= /* @escapeNotVerified */ $block->setValidationContainer('bundle-option-' . $_option->getId() . '-' . $_selection->getSelectionId(), 'bundle-option-' . $_option->getId() . '-container') ?> + <?php if ($_option->getRequired()) : ?> + <?= /* @noEscape */ $block->setValidationContainer('bundle-option-' . $_option->getId() . '-' . $_selection->getSelectionId(), 'bundle-option-' . $_option->getId() . '-container') ?> <?php endif;?> </div> <?php endforeach; ?> - <div id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></div> + <div id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></div> <?php endif; ?> </div> </div> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/multi.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/multi.phtml index 8c13dd6479d4d..f4c4e3e51ae09 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/multi.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/multi.phtml @@ -3,32 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Options\Type\Multi */ ?> <?php $_option = $block->getOption(); ?> <?php $_selections = $_option->getSelections(); ?> -<?php $_skipSaleableCheck = $this->helper('Magento\Catalog\Helper\Product')->getSkipSaleableCheck(); ?> -<div class="field admin__field <?php if ($_option->getRequired()) echo ' required' ?><?php if ($_option->getDecoratedIsLast()):?> last<?php endif; ?>"> +<?php $_skipSaleableCheck = $this->helper(Magento\Catalog\Helper\Product::class)->getSkipSaleableCheck(); ?> +<div class="field admin__field <?php if ($_option->getRequired()) { echo ' required'; } ?><?php if ($_option->getDecoratedIsLast()) :?> last<?php endif; ?>"> <label class="label admin__field-label"><span><?= $block->escapeHtml($_option->getTitle()) ?></span></label> <div class="control admin__field-control"> - <?php if (count($_selections) == 1 && $_option->getRequired()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> - <input type="hidden" name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selections[0]) ?>" /> - <?php else: ?> - <select multiple="multiple" size="5" id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][]" - class="admin__control-multiselect bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?><?php if ($_option->getRequired()) echo ' required-entry' ?> multiselect change-container-classname" + <?php if (count($_selections) == 1 && $_option->getRequired()) : ?> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> + <input type="hidden" name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>" + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selections[0])) ?>" /> + <?php else : ?> + <select multiple="multiple" size="5" id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][]" + class="admin__control-multiselect bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?><?php if ($_option->getRequired()) { echo ' required-entry'; } ?> multiselect change-container-classname" onchange="ProductConfigure.bundleControl.changeSelection(this)"> - <?php if(!$_option->getRequired()): ?> - <option value=""><?= /* @escapeNotVerified */ __('None') ?></option> + <?php if (!$_option->getRequired()) : ?> + <option value=""><?= $block->escapeHtml(__('None')) ?></option> <?php endif; ?> - <?php foreach ($_selections as $_selection): ?> - <option value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"<?php if ($block->isSelected($_selection)) echo ' selected="selected"' ?><?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) echo ' disabled="disabled"' ?> price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selection) ?>"><?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection, false) ?></option> + <?php foreach ($_selections as $_selection) : ?> + <option value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + <?php if ($block->isSelected($_selection)) { echo ' selected="selected"'; } ?> + <?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) { echo ' disabled="disabled"'; } ?> + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selection)) ?>"> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selection, false) ?></option> <?php endforeach; ?> </select> <?php endif; ?> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/radio.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/radio.phtml index f0912979a9248..0c3835fb32af8 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/radio.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/radio.phtml @@ -3,69 +3,73 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Options\Type\Radio */ ?> <?php $_option = $block->getOption(); ?> <?php $_selections = $_option->getSelections(); ?> <?php $_default = $_option->getDefaultSelection(); ?> -<?php $_skipSaleableCheck = $this->helper('Magento\Catalog\Helper\Product')->getSkipSaleableCheck(); ?> +<?php $_skipSaleableCheck = $this->helper(Magento\Catalog\Helper\Product::class)->getSkipSaleableCheck(); ?> <?php list($_defaultQty, $_canChangeQty) = $block->getDefaultValues(); ?> -<div class="field admin__field options<?php if ($_option->getRequired()) echo ' required' ?>"> +<div class="field admin__field options<?php if ($_option->getRequired()) { echo ' required'; } ?>"> <label class="label admin__field-label"><span><?= $block->escapeHtml($_option->getTitle()) ?></span></label> <div class="control admin__field-control"> - <div class="nested<?php if ($_option->getDecoratedIsLast()):?> last<?php endif; ?>"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> + <div class="nested<?php if ($_option->getDecoratedIsLast()) :?> last<?php endif; ?>"> + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selections[0]) ?> <input type="hidden" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selections[0]) ?>" /> - <?php else:?> - <?php if (!$_option->getRequired()): ?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>" + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selections[0])) ?>" /> + <?php else :?> + <?php if (!$_option->getRequired()) : ?> <div class="field choice admin__field admin__field-option"> <input type="radio" class="radio admin__control-radio" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]"<?= ($_default && $_default->isSalable()) ? '' : ' checked="checked" ' ?> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]"<?= ($_default && $_default->isSalable()) ? '' : ' checked="checked" ' ?> value="" onclick="ProductConfigure.bundleControl.changeSelection(this)" /> <label class="admin__field-label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>"><span><?= /* @escapeNotVerified */ __('None') ?></span></label> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>"><span><?= $block->escapeHtml(__('None')) ?></span></label> </div> <?php endif; ?> - <?php foreach ($_selections as $_selection): ?> + <?php foreach ($_selections as $_selection) : ?> <div class="field choice admin__field admin__field-option"> <input type="radio" class="radio admin__control-radio <?= $_option->getRequired() ? ' validate-one-required-by-name' : '' ?> change-container-classname" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - <?php if ($block->isSelected($_selection)) echo ' checked="checked"' ?><?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) echo ' disabled="disabled"' ?> - value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + <?php if ($block->isSelected($_selection)) { echo ' checked="checked"'; } ?> + <?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) { echo ' disabled="disabled"'; } ?> + value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" onclick="ProductConfigure.bundleControl.changeSelection(this)" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selection) ?>" - qtyId="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input" /> + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selection)) ?>" + qtyId="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input" /> <label class="admin__field-label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"><span><?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection) ?></span></label> - <?php if ($_option->getRequired()): ?> - <?= /* @escapeNotVerified */ $block->setValidationContainer('bundle-option-'.$_option->getId().'-'.$_selection->getSelectionId(), 'bundle-option-'.$_option->getId().'-container') ?> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"> + <span><?= /* @noEscape */ $block->getSelectionTitlePrice($_selection) ?></span> + </label> + <?php if ($_option->getRequired()) : ?> + <?= /* @noEscape */ $block->setValidationContainer('bundle-option-'.$_option->getId().'-'.$_selection->getSelectionId(), 'bundle-option-'.$_option->getId().'-container') ?> <?php endif; ?> </div> <?php endforeach; ?> - <div id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></div> + <div id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></div> <?php endif; ?> <div class="field admin__field qty"> <label class="label admin__field-label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input"><span><?= /* @escapeNotVerified */ __('Quantity:') ?></span></label> - <div class="control admin__field-control"><input <?php if (!$_canChangeQty) echo ' disabled="disabled"' ?> - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input" - class="input-text admin__control-text qty<?php if (!$_canChangeQty) echo ' qty-disabled' ?>" + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input"> + <span><?= $block->escapeHtml(__('Quantity:')) ?></span> + </label> + <div class="control admin__field-control"><input <?php if (!$_canChangeQty) { echo ' disabled="disabled"'; } ?> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input" + class="input-text admin__control-text qty<?php if (!$_canChangeQty) { echo ' qty-disabled'; } ?>" type="text" - name="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" value="<?= /* @escapeNotVerified */ $_defaultQty ?>" /> + name="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_defaultQty) ?>" /> </div> </div> </div> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/select.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/select.phtml index 32766f62163ed..fbb7f7fbb7b38 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/select.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/type/select.phtml @@ -3,36 +3,39 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Options\Type\Select */ ?> <?php $_option = $block->getOption(); ?> <?php $_selections = $_option->getSelections(); ?> <?php $_default = $_option->getDefaultSelection(); ?> -<?php $_skipSaleableCheck = $this->helper('Magento\Catalog\Helper\Product')->getSkipSaleableCheck(); ?> +<?php $_skipSaleableCheck = $this->helper(Magento\Catalog\Helper\Product::class)->getSkipSaleableCheck(); ?> <?php list($_defaultQty, $_canChangeQty) = $block->getDefaultValues(); ?> -<div class="field admin__field option<?php if ($_option->getDecoratedIsLast()):?> last<?php endif; ?><?php if ($_option->getRequired()) echo ' required _required' ?>"> +<div class="field admin__field option<?php if ($_option->getDecoratedIsLast()) :?> last<?php endif; ?><?php if ($_option->getRequired()) { echo ' required _required'; } ?>"> <label class="label admin__field-label"><span><?= $block->escapeHtml($_option->getTitle()) ?></span></label> <div class="control admin__field-control"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> - <input type="hidden" name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>" - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selections[0]) ?>" /> - <?php else:?> - <select id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?><?php if ($_option->getRequired()) echo ' required-entry' ?> select admin__control-select change-container-classname" + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selections[0]) ?> + <input type="hidden" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>" + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selections[0])) ?>" /> + <?php else :?> + <select id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?><?php if ($_option->getRequired()) { echo ' required-entry'; } ?> select admin__control-select change-container-classname" onchange="ProductConfigure.bundleControl.changeSelection(this)"> - <option value=""><?= /* @escapeNotVerified */ __('Choose a selection...') ?></option> - <?php foreach ($_selections as $_selection): ?> + <option value=""><?= $block->escapeHtml(__('Choose a selection...')) ?></option> + <?php foreach ($_selections as $_selection) : ?> <option - value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"<?php if ($block->isSelected($_selection)) echo ' selected="selected"' ?><?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) echo ' disabled="disabled"' ?> - price="<?= /* @escapeNotVerified */ $block->getSelectionPrice($_selection) ?>" - qtyId="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input"><?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection, false) ?></option> + value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + <?php if ($block->isSelected($_selection)) { echo ' selected="selected"'; } ?> + <?php if (!$_selection->isSaleable() && !$_skipSaleableCheck) { echo ' disabled="disabled"'; } ?> + price="<?= $block->escapeHtmlAttr($block->getSelectionPrice($_selection)) ?>" + qtyId="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input"> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selection, false) ?> + </option> <?php endforeach; ?> </select> <?php endif; ?> @@ -40,12 +43,16 @@ <div class="nested"> <div class="field admin__field qty"> <label class="label admin__field-label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input"><span><?= /* @escapeNotVerified */ __('Quantity:') ?></span></label> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input"> + <span><?= $block->escapeHtml(__('Quantity:')) ?></span> + </label> <div class="control admin__field-control"> - <input <?php if (!$_canChangeQty) echo ' disabled="disabled"' ?> - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input" - class="input-text admin__control-text qty<?php if (!$_canChangeQty) echo ' qty-disabled' ?>" type="text" - name="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" value="<?= /* @escapeNotVerified */ $_defaultQty ?>" /> + <input <?php if (!$_canChangeQty) { echo ' disabled="disabled"'; } ?> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input" + class="input-text admin__control-text qty<?php if (!$_canChangeQty) { echo ' qty-disabled'; } ?>" + type="text" + name="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_defaultQty) ?>" /> </div> </div> </div> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml index 5b27412dd885b..c8ab6cc5b98d2 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Bundle */ ?> <script> @@ -19,14 +17,20 @@ if(typeof Bundle=='undefined') { <div id="bundle_product_container" class="entry-edit form-inline"> <fieldset class="fieldset"> <div class="field field-ship-bundle-items"> - <label for="shipment_type" class="label"><?= /* @escapeNotVerified */ __('Ship Bundle Items') ?></label> + <label for="shipment_type" class="label"><?= $block->escapeHtml(__('Ship Bundle Items')) ?></label> <div class="control"> - <select <?php if ($block->isReadonly()): ?>disabled="disabled" <?php endif;?> + <select <?php if ($block->isReadonly()) : ?>disabled="disabled" <?php endif;?> id="shipment_type" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[shipment_type]" + name="<?= $block->escapeHtmlAttr($block->getFieldSuffix()) ?>[shipment_type]" class="select"> - <option value="1"><?= /* @escapeNotVerified */ __('Separately') ?></option> - <option value="0"<?php if ($block->getProduct()->getShipmentType() == 0): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('Together') ?></option> + <option value="1"><?= $block->escapeHtml(__('Separately')) ?></option> + <option value="0" + <?php if ($block->getProduct()->getShipmentType() == 0) : ?> + selected="selected" + <?php endif; ?> + > + <?= $block->escapeHtml(__('Together')) ?> + </option> </select> </div> </div> @@ -48,7 +52,7 @@ require(["prototype", "mage/adminhtml/form"], function(){ // re-bind form elements onchange varienWindowOnload(true); - <?php if ($block->isReadonly()):?> + <?php if ($block->isReadonly()) :?> $('product_bundle_container').select('input', 'select', 'textarea', 'button').each(function(input){ input.disabled = true; if (input.tagName.toLowerCase() == 'button') { diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml index 783d71beb1646..4d68d363b7484 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option.phtml @@ -3,16 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Bundle\Option */ ?> <script id="bundle-option-template" type="text/x-magento-template"> - <div id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>" class="option-box"> - <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>-wrapper"> + <div id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>" class="option-box"> + <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-wrapper"> <div class="fieldset-wrapper-title"> - <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>-content"> + <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-content"> <span><%- data.default_title %></span> </strong> <div class="actions"> @@ -20,55 +18,56 @@ </div> <div data-role="draggable-handle" class="draggable-handle"></div> </div> - <div class="fieldset-wrapper-content in collapse" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>-content"> + <div class="fieldset-wrapper-content in collapse" id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>-content"> <fieldset class="fieldset"> <fieldset class="fieldset-alt"> <div class="field field-option-title required"> - <label class="label" for="id_<?= /* @escapeNotVerified */ $block->getFieldName() ?>_<%- data.index %>_title"> - <?= /* @escapeNotVerified */ __('Option Title') ?> + <label class="label" for="id_<?= $block->escapeHtmlAttr($block->getFieldName()) ?>_<%- data.index %>_title"> + <?= $block->escapeHtml(__('Option Title')) ?> </label> <div class="control"> - <?php if ($block->isDefaultStore()): ?> + <?php if ($block->isDefaultStore()) : ?> <input class="input-text required-entry" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][title]" - id="id_<?= /* @escapeNotVerified */ $block->getFieldName() ?>_<%- data.index %>_title" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][title]" + id="id_<?= $block->escapeHtmlAttr($block->getFieldName()) ?>_<%- data.index %>_title" value="<%- data.title %>" data-original-value="<%- data.title %>" /> - <?php else: ?> + <?php else : ?> <input class="input-text required-entry" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][default_title]" - id="id_<?= /* @escapeNotVerified */ $block->getFieldName() ?>_<%- data.index %>_default_title" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][default_title]" + id="id_<?= $block->escapeHtmlAttr($block->getFieldName()) ?>_<%- data.index %>_default_title" value="<%- data.default_title %>" data-original-value="<%- data.default_title %>" /> <?php endif; ?> <input type="hidden" - id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_id_<%- data.index %>" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][option_id]" + id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_id_<%- data.index %>" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][option_id]" value="<%- data.option_id %>" /> <input type="hidden" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][delete]" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][delete]" value="" data-state="deleted" /> </div> </div> - <?php if (!$block->isDefaultStore()): ?> + <?php if (!$block->isDefaultStore()) : ?> <div class="field field-option-store-view required"> - <label class="label" for="id_<?= /* @escapeNotVerified */ $block->getFieldName() ?>_<%- data.index %>_title_store"> - <?= /* @escapeNotVerified */ __('Store View Title') ?> + <label class="label" for="id_<?= $block->escapeHtmlAttr($block->getFieldName()) ?>_<%- data.index %>_title_store"> + <?= $block->escapeHtml(__('Store View Title')) ?> </label> <div class="control"> - <input class="input-text required-entry" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][title]" - id="id_<?= /* @escapeNotVerified */ $block->getFieldName() ?>_<%- data.index %>_title_store" + <input class="input-text required-entry" + type="text" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][title]" + id="id_<?= $block->escapeHtmlAttr($block->getFieldName()) ?>_<%- data.index %>_title_store" value="<%- data.title %>" /> </div> </div> <?php endif; ?> <div class="field field-option-input-type required"> - <label class="label" for="<?= /* @escapeNotVerified */ $block->getFieldId() . '_<%- data.index %>_type' ?>"> - <?= /* @escapeNotVerified */ __('Input Type') ?> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId() . '_<%- data.index %>_type') ?>"> + <?= $block->escapeHtml(__('Input Type')) ?> </label> <div class="control"> <?= $block->getTypeSelectHtml() ?> @@ -81,19 +80,19 @@ checked="checked" id="field-option-req" /> <label for="field-option-req"> - <?= /* @escapeNotVerified */ __('Required') ?> + <?= $block->escapeHtml(__('Required')) ?> </label> <span style="display:none"><?= $block->getRequireSelectHtml() ?></span> </div> </div> <div class="field field-option-position no-display"> <label class="label" for="field-option-position"> - <?= /* @escapeNotVerified */ __('Position') ?> + <?= $block->escapeHtml(__('Position')) ?> </label> <div class="control"> <input class="input-text validate-zero-or-greater" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.index %>][position]" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.index %>][position]" value="<%- data.position %>" id="field-option-position" /> </div> @@ -101,13 +100,13 @@ </fieldset> <div class="no-products-message"> - <?= /* @escapeNotVerified */ __('There are no products in this option.') ?> + <?= $block->escapeHtml(__('There are no products in this option.')) ?> </div> <?= $block->getAddSelectionButtonHtml() ?> </fieldset> </div> </div> - <div id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_search_<%- data.index %>" class="selection-search"></div> + <div id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_search_<%- data.index %>" class="selection-search"></div> </div> </script> @@ -141,7 +140,7 @@ function changeInputType(oldObject, oType) { Bundle.Option = Class.create(); Bundle.Option.prototype = { - idLabel : '<?= /* @escapeNotVerified */ $block->getFieldId() ?>', + idLabel : '<?= $block->escapeJs($block->getFieldId()) ?>', templateText : '', itemsCount : 0, initialize : function(template) { @@ -150,7 +149,7 @@ Bundle.Option.prototype = { add : function(data) { if (!data) { - data = <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode(['default_title' => __('New Option')]) ?>; + data = <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode(['default_title' => __('New Option')]) ?>; } else { data.title = data.title.replace(/</g, "<"); data.title = data.title.replace(/"/g, """); @@ -280,17 +279,17 @@ Bundle.Option.prototype = { var optionIndex = 0; bOption = new Bundle.Option(optionTemplate); <?php - foreach ($block->getOptions() as $_option) { - /** @var $_option \Magento\Bundle\Model\Option */ - /* @escapeNotVerified */ echo 'optionIndex = bOption.add(', $_option->toJson(), ');', PHP_EOL; - if ($_option->getSelections()) { - foreach ($_option->getSelections() as $_selection) { - /** @var $_selection \Magento\Catalog\Model\Product */ - $_selection->setName($block->escapeHtml($_selection->getName())); - /* @escapeNotVerified */ echo 'bSelection.addRow(optionIndex,', $_selection->toJson(), ');', PHP_EOL; - } +foreach ($block->getOptions() as $_option) { + /** @var $_option \Magento\Bundle\Model\Option */ + /* @noEscape */ echo 'optionIndex = bOption.add(', $_option->toJson(), ');', PHP_EOL; + if ($_option->getSelections()) { + foreach ($_option->getSelections() as $_selection) { + /** @var $_selection \Magento\Catalog\Model\Product */ + $_selection->setName($block->escapeHtml($_selection->getName())); + /* @noEscape */ echo 'bSelection.addRow(optionIndex,', $_selection->toJson(), ');', PHP_EOL; } } +} ?> function togglePriceType() { bOption['priceType' + ($('price_type').value == '1' ? 'Fixed' : 'Dynamic')](); diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option/selection.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option/selection.phtml index 91c245afe5717..0f1167f3d3eaa 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option/selection.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle/option/selection.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Bundle\Option\Selection */ ?> <script id="bundle-option-selection-box-template" type="text/x-magento-template"> @@ -13,16 +11,16 @@ <thead> <tr class="headings"> <th class="col-draggable"></th> - <th class="col-default"><?= /* @escapeNotVerified */ __('Default') ?></th> - <th class="col-name"><?= /* @escapeNotVerified */ __('Name') ?></th> - <th class="col-sku"><?= /* @escapeNotVerified */ __('SKU') ?></th> - <?php if ($block->getCanReadPrice() !== false): ?> - <th class="col-price price-type-box"><?= /* @escapeNotVerified */ __('Price') ?></th> - <th class="col-price price-type-box"><?= /* @escapeNotVerified */ __('Price Type') ?></th> + <th class="col-default"><?= $block->escapeHtml(__('Default')) ?></th> + <th class="col-name"><?= $block->escapeHtml(__('Name')) ?></th> + <th class="col-sku"><?= $block->escapeHtml(__('SKU')) ?></th> + <?php if ($block->getCanReadPrice() !== false) : ?> + <th class="col-price price-type-box"><?= $block->escapeHtml(__('Price')) ?></th> + <th class="col-price price-type-box"><?= $block->escapeHtml(__('Price Type')) ?></th> <?php endif; ?> - <th class="col-qty"><?= /* @escapeNotVerified */ __('Default Quantity') ?></th> - <th class="col-uqty qty-box"><?= /* @escapeNotVerified */ __('User Defined') ?></th> - <th class="col-order type-order" style="display:none"><?= /* @escapeNotVerified */ __('Position') ?></th> + <th class="col-qty"><?= $block->escapeHtml(__('Default Quantity')) ?></th> + <th class="col-uqty qty-box"><?= $block->escapeHtml(__('User Defined')) ?></th> + <th class="col-order type-order" style="display:none"><?= $block->escapeHtml(__('Position')) ?></th> <th class="col-actions"></th> </tr> </thead> @@ -33,31 +31,38 @@ <script id="bundle-option-selection-row-template" type="text/x-magento-template"> <td class="col-draggable"> <span data-role="draggable-handle" class="draggable-handle"></span> - <input type="hidden" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_id<%- data.index %>" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][selection_id]" + <input type="hidden" + id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_id<%- data.index %>" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][selection_id]" value="<%- data.selection_id %>"/> - <input type="hidden" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][option_id]" + <input type="hidden" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][option_id]" value="<%- data.option_id %>"/> - <input type="hidden" class="product" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][product_id]" + <input type="hidden" + class="product" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][product_id]" value="<%- data.product_id %>"/> - <input type="hidden" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][delete]" - value="" class="delete"/> + <input type="hidden" name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][delete]" + value="" + class="delete"/> </td> <td class="col-default"> - <input onclick="bSelection.checkGroup(event)" type="<%- data.option_type %>" class="default" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][is_default]" + <input onclick="bSelection.checkGroup(event)" + type="<%- data.option_type %>" + class="default" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][is_default]" value="1" <%- data.checked %> /> </td> <td class="col-name"><%- data.name %></td> <td class="col-sku"><%- data.sku %></td> -<?php if ($block->getCanReadPrice() !== false): ?> +<?php if ($block->getCanReadPrice() !== false) : ?> <td class="col-price price-type-box"> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>_price_value" - class="input-text required-entry validate-zero-or-greater" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_value]" + <input id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>_price_value" + class="input-text required-entry validate-zero-or-greater" + type="text" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_value]" value="<%- data.selection_price_value %>" - <?php if ($block->getCanEditPrice() === false): ?> + <?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled" <?php endif; ?>/> </td> @@ -65,19 +70,23 @@ <?= $block->getPriceTypeSelectHtml() ?> <div><?= $block->getCheckboxScopeHtml() ?></div> </td> -<?php else: ?> - <input type="hidden" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>_price_value" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_value]" value="0" /> - <input type="hidden" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>_price_type" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_type]" value="0" /> - <?php if ($block->isUsedWebsitePrice()): ?> - <input type="hidden" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.index %>_price_scope" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][default_price_scope]" value="1" /> +<?php else : ?> + <input type="hidden" + id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>_price_value" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_value]" value="0" /> + <input type="hidden" + id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>_price_type" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][selection_price_type]" value="0" /> + <?php if ($block->isUsedWebsitePrice()) : ?> + <input type="hidden" + id="<?= $block->escapeHtmlAttr($block->getFieldId()) ?>_<%- data.index %>_price_scope" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][default_price_scope]" value="1" /> <?php endif; ?> <?php endif; ?> <td class="col-qty"> - <input class="input-text required-entry validate-greater-zero-based-on-option validate-zero-or-greater" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][selection_qty]" + <input class="input-text required-entry validate-greater-zero-based-on-option validate-zero-or-greater" + type="text" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][selection_qty]" value="<%- data.selection_qty %>" /> </td> <td class="col-uqty qty-box"> @@ -85,8 +94,9 @@ <span style="display:none"><?= $block->getQtyTypeSelectHtml() ?></span> </td> <td class="col-order type-order" style="display:none"> - <input class="input-text required-entry validate-zero-or-greater" type="text" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.parentIndex %>][<%- data.index %>][position]" + <input class="input-text required-entry validate-zero-or-greater" + type="text" + name="<?= $block->escapeHtmlAttr($block->getFieldName()) ?>[<%- data.parentIndex %>][<%- data.index %>][position]" value="<%- data.position %>" /> </td> <td class="col-actions"> @@ -106,7 +116,7 @@ var bundleTemplateBox = jQuery('#bundle-option-selection-box-template').html(), Bundle.Selection = Class.create(); Bundle.Selection.prototype = { - idLabel : '<?= /* @escapeNotVerified */ $block->getFieldId() ?>', + idLabel : '<?= $block->escapeJs($block->getFieldId()) ?>', scopePrice : <?= (int)$block->isUsedWebsitePrice() ?>, templateBox : '', templateRow : '', @@ -115,7 +125,7 @@ Bundle.Selection.prototype = { gridSelection: new Hash(), gridRemoval: new Hash(), gridSelectedProductSkus: [], - selectionSearchUrl: '<?= /* @escapeNotVerified */ $block->getSelectionSearchUrl() ?>', + selectionSearchUrl: '<?= $block->escapeUrl($block->getSelectionSearchUrl()) ?>', initialize : function() { this.templateBox = '<div class="tier form-list" id="' + this.idLabel + '_box_<%- data.parentIndex %>">' + bundleTemplateBox + '</div>'; diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml index 3aba02fadffbb..c480d9b126da6 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/create/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -21,19 +19,19 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td> </td> <td> </td> <td> </td> @@ -43,165 +41,164 @@ <td> </td> <td class="last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index == $_count && !$_showlastRow) ? ' class="border"' : '' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"><div class="option-value"><?= $block->getValueHtml($_item) ?></div></td> <?php endif; ?> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'price') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-ordered-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyOrdered() * 1 ?></td> </tr> - <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Invoiced') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyInvoiced()*1 ?></td> + <th><?= $block->escapeHtml(__('Invoiced')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyInvoiced() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $block->isShipmentSeparately($_item)): ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $block->isShipmentSeparately($_item)) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyRefunded()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Refunded') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyRefunded()*1 ?></td> + <th><?= $block->escapeHtml(__('Refunded')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyRefunded() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyCanceled()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Canceled') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyCanceled()*1 ?></td> + <th><?= $block->escapeHtml(__('Canceled')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyCanceled() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php elseif ($block->isShipmentSeparately($_item)): ?> + <?php elseif ($block->isShipmentSeparately($_item)) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyOrdered() * 1 ?></td> </tr> - <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <?php if ($block->canParentReturnToStock($_item)) : ?> <td class="col-return-to-stock"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?php if ($block->canReturnItemToStock($_item)) : ?> <input type="checkbox" class="admin__control-checkbox" - name="creditmemo[items][<?= /* @escapeNotVerified */ $_item->getOrderItemId() ?>][back_to_stock]" - value="1"<?php if ($_item->getBackToStock()):?> checked="checked"<?php endif;?> /> + name="creditmemo[items][<?= $block->escapeHtmlAttr($_item->getOrderItemId()) ?>][back_to_stock]" + value="1"<?php if ($_item->getBackToStock()) :?> checked="checked"<?php endif;?> /> <label class="admin__field-label"></label> <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <?php endif; ?> <td class="col-refund col-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?php if ($block->canEditQty()) : ?> <input type="text" class="input-text admin__control-text qty-input" - name="creditmemo[items][<?= /* @escapeNotVerified */ $_item->getOrderItemId() ?>][qty]" - value="<?= /* @escapeNotVerified */ $_item->getQty()*1 ?>" /> - <?php else: ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> + name="creditmemo[items][<?= $block->escapeHtmlAttr($_item->getOrderItemId()) ?>][qty]" + value="<?= (float)$_item->getQty() * 1 ?>" /> + <?php else : ?> + <?= (float)$_item->getQty() * 1 ?> <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-subtotal"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'subtotal') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax-amount"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('tax_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('tax_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-discont"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('discount_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-total last"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'total') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions($_item->getOrderItem())): ?> + <?php if ($block->getOrderOptions($_item->getOrderItem())) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> </dl> - <?php else: ?> + <?php else : ?>   <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/view/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/view/items/renderer.phtml index a9e71a79f8977..0d54e1528dfe9 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/view/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/creditmemo/view/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -21,19 +19,19 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td> </td> <td> </td> <td> </td> @@ -41,88 +39,87 @@ <td> </td> <td class="last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index == $_count && !$_showlastRow) ? ' class="border"' : '' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"><div class="option-value"><?= $block->getValueHtml($_item) ?></div></td> <?php endif; ?> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'price') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-subtotal"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'subtotal') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('tax_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('tax_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-discount"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('discount_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-total last"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'total') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions()): ?> + <?php if ($block->getOrderOptions()) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions() as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions() as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml index 12da960a9c6cf..a7d49b4b3530a 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -21,28 +19,28 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $shipTogether = ($_item->getOrderItem()->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) ? !$_item->getOrderItem()->isShipSeparately() : !$_item->getOrderItem()->getParentItem()->isShipSeparately() ?> <?php $block->setPriceDataObject($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php - if ($shipTogether) { - continue; - } + if ($shipTogether) { + continue; + } ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td> </td> <td> </td> <td> </td> @@ -51,152 +49,152 @@ <td> </td> <td class="last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index == $_count && !$_showlastRow) ? ' class="border"' : '' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"> <div class="option-value"><?= $block->getValueHtml($_item) ?></div> </td> <?php endif; ?> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether) : ?> <?= $block->getColumnHtml($_item, 'price') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-qty"> - <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><span><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyOrdered()*1 ?></span></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><span><?= (float)$_item->getOrderItem()->getQtyOrdered() * 1 ?></span></td> </tr> - <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyInvoiced()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Invoiced') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyInvoiced()*1 ?></td> + <th><?= $block->escapeHtml(__('Invoiced')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyInvoiced() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $block->isShipmentSeparately($_item)): ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped() && $block->isShipmentSeparately($_item)) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyRefunded()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyRefunded()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Refunded') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyRefunded()*1 ?></td> + <th><?= $block->escapeHtml(__('Refunded')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyRefunded() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getOrderItem()->getQtyCanceled()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyCanceled()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Canceled') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyCanceled()*1 ?></td> + <th><?= $block->escapeHtml(__('Canceled')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyCanceled() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php elseif ($block->isShipmentSeparately($_item)): ?> + <?php elseif ($block->isShipmentSeparately($_item)) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyOrdered() * 1 ?></td> </tr> - <?php if ((float) $_item->getOrderItem()->getQtyShipped()): ?> + <?php if ((float) $_item->getOrderItem()->getQtyShipped()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getOrderItem()->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getOrderItem()->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-qty-invoice"> - <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether) : ?> <?php if ($block->canEditQty()) : ?> <input type="text" class="input-text admin__control-text qty-input" - name="invoice[items][<?= /* @escapeNotVerified */ $_item->getOrderItemId() ?>]" - value="<?= /* @escapeNotVerified */ $_item->getQty()*1 ?>" /> + name="invoice[items][<?= $block->escapeHtmlAttr($_item->getOrderItemId()) ?>]" + value="<?= (float)$_item->getQty() * 1 ?>" /> <?php else : ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> + <?= (float)$_item->getQty() * 1 ?> <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-subtotal"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'subtotal') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('tax_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('tax_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-discount"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('discount_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-total last"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'total') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions($_item->getOrderItem())): ?> + <?php if ($block->getOrderOptions($_item->getOrderItem())) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); -}); -</script> + }); + </script> <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> </dl> - <?php else: ?> + <?php else : ?>   <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/view/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/view/items/renderer.phtml index 5f344409b6a4c..e29bb5dbc9479 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/view/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/view/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -21,19 +19,19 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td> </td> <td> </td> <td> </td> @@ -41,89 +39,88 @@ <td> </td> <td class="last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index == $_count && !$_showlastRow) ? ' class="border"' : '' ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> - <?php else: ?> + <?php else : ?> <td class="col-product"> <div class="option-value"><?= $block->getValueHtml($_item) ?></div> </td> <?php endif; ?> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'price') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-subtotal"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'subtotal') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('tax_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('tax_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-discount"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('discount_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-total last"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'total') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions()): ?> + <?php if ($block->getOrderOptions()) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions() as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions() as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['protoype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml index bb0857a80d689..233e57a003397 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/order/view/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -16,24 +14,24 @@ <?php $_item = $block->getItem() ?> <?php $items = array_merge([$_item], $_item->getChildrenItems()); ?> -<?php $_count = count ($items) ?> +<?php $_count = count($items) ?> <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> -<?php if($block->getOrderOptions() || $_item->getDescription() || $block->canDisplayGiftmessage()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription() || $block->canDisplayGiftmessage()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_item->getParentItem()): ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_item->getParentItem()) : ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td> </td> <td> </td> <td> </td> @@ -44,161 +42,160 @@ <td> </td> <td class="last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index==$_count && !$_showlastRow)?' class="border"':'' ?>> - <?php if (!$_item->getParentItem()): ?> + <?php if (!$_item->getParentItem()) : ?> <td class="col-product"> - <div class="product-title" id="order_item_<?= /* @escapeNotVerified */ $_item->getId() ?>_title"> + <div class="product-title" id="order_item_<?= $block->escapeHtmlAttr($_item->getId()) ?>_title"> <?= $block->escapeHtml($_item->getName()) ?> </div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"> <div class="option-value"><?= $block->getValueHtml($_item) ?></div> </td> <?php endif; ?> <td class="col-status"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getStatus() ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= $block->escapeHtml($_item->getStatus()) ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-price-original"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('original_price') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('original_price') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'price') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-ordered-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><?= (float)$_item->getQtyOrdered() * 1 ?></td> </tr> - <?php if ((float) $_item->getQtyInvoiced()): ?> + <?php if ((float) $_item->getQtyInvoiced()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Invoiced') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyInvoiced()*1 ?></td> + <th><?= $block->escapeHtml(__('Invoiced')) ?></th> + <td><?= (float)$_item->getQtyInvoiced() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyShipped() && $block->isShipmentSeparately($_item)): ?> + <?php if ((float) $_item->getQtyShipped() && $block->isShipmentSeparately($_item)) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyRefunded()): ?> + <?php if ((float) $_item->getQtyRefunded()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Refunded') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyRefunded()*1 ?></td> + <th><?= $block->escapeHtml(__('Refunded')) ?></th> + <td><?= (float)$_item->getQtyRefunded() * 1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyCanceled()): ?> + <?php if ((float) $_item->getQtyCanceled()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Canceled') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyCanceled()*1 ?></td> + <th><?= $block->escapeHtml(__('Canceled')) ?></th> + <td><?= (float)$_item->getQtyCanceled() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php elseif ($block->isShipmentSeparately($_item)): ?> + <?php elseif ($block->isShipmentSeparately($_item)) : ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')) ?></th> + <td><?= (float)$_item->getQtyOrdered() * 1 ?></td> </tr> - <?php if ((float) $_item->getQtyShipped()): ?> + <?php if ((float) $_item->getQtyShipped()) : ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')) ?></th> + <td><?= (float)$_item->getQtyShipped() * 1 ?></td> </tr> <?php endif; ?> </table> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-subtotal"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'subtotal') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax-amount"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('tax_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('tax_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-tax-percent"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayTaxPercent($_item) ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayTaxPercent($_item) ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-discont"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->displayPriceAttribute('discount_amount') ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->displayPriceAttribute('discount_amount') ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-total last"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getColumnHtml($_item, 'total') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if($_showlastRow): ?> - <tr<?php if (!$block->canDisplayGiftmessage()) echo ' class="border"' ?>> +<?php if ($_showlastRow) : ?> + <tr<?php if (!$block->canDisplayGiftmessage()) { echo ' class="border"'; } ?>> <td class="col-product"> - <?php if ($block->getOrderOptions()): ?> + <?php if ($block->getOrderOptions()) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions() as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?>:</dt> + <?php foreach ($block->getOrderOptions() as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?>:</dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> </dl> - <?php else: ?> + <?php else : ?>   <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/create/items/renderer.phtml index 2ede8277bcfc9..2e52ed906626b 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/create/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/create/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Adminhtml\Sales\Order\Items\Renderer */ ?> @@ -17,85 +15,84 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td class="col-product"> </td> <td class="col-qty last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr class="<?= (++$_index == $_count && !$_showlastRow) ? 'border' : '' ?>"> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"><div class="option-value"><?= $block->getValueHtml($_item) ?></div></td> <?php endif; ?> <td class="col-ordered-qty"> - <?php if ($block->isShipmentSeparately($_item)): ?> + <?php if ($block->isShipmentSeparately($_item)) : ?> <?= $block->getColumnHtml($_item, 'qty') ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> <td class="col-qty last"> - <?php if ($block->isShipmentSeparately($_item)): ?> + <?php if ($block->isShipmentSeparately($_item)) : ?> <input type="text" class="input-text admin__control-text" - name="shipment[items][<?= /* @escapeNotVerified */ $_item->getOrderItemId() ?>]" - value="<?= /* @escapeNotVerified */ $_item->getQty()*1 ?>" /> - <?php else: ?> + name="shipment[items][<?= $block->escapeHtmlAttr($_item->getOrderItemId()) ?>]" + value="<?= (float)$_item->getQty() * 1 ?>" /> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions($_item->getOrderItem())): ?> + <?php if ($block->getOrderOptions($_item->getOrderItem())) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> <?php endforeach; ?> </dl> - <?php else: ?> + <?php else : ?>   <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/view/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/view/items/renderer.phtml index 71eabd45cbb57..521669700e10a 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/view/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/shipment/view/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Adminhtml\Sales\Order\Items\Renderer */ ?> @@ -17,74 +15,73 @@ <?php $_prevOptionId = '' ?> -<?php if ($block->getOrderOptions() || $_item->getDescription()): ?> +<?php if ($block->getOrderOptions() || $_item->getDescription()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php $block->setPriceDataObject($_item) ?> - <?php if ($_item->getParentItem()): ?> + <?php if ($_item->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr> - <td class="col-product"><div class="option-label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col-product"><div class="option-label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> <td class="col-qty last"> </td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> <tr<?= (++$_index == $_count && !$_showlastRow) ? ' class="border"' : '' ?>> - <?php if (!$_item->getParentItem()): ?> + <?php if (!$_item->getParentItem()) : ?> <td class="col-product"> <div class="product-title"><?= $block->escapeHtml($_item->getName()) ?></div> <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> - <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($_item->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU')) ?>:</span> + <?= /* @noEscape */ implode('<br />', $this->helper(Magento\Catalog\Helper\Data::class)->splitSku($_item->getSku())) ?> </div> </td> - <?php else: ?> + <?php else : ?> <td class="col-product"><div class="option-value"><?= $block->getValueHtml($_item) ?></div></td> <?php endif; ?> <td class="col-qty last"> - <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())): ?> - <?php if (isset($shipItems[$_item->getId()])): ?> - <?= /* @escapeNotVerified */ $shipItems[$_item->getId()]->getQty()*1 ?> - <?php elseif ($_item->getIsVirtual()): ?> - <?= /* @escapeNotVerified */ __('N/A') ?> - <?php else: ?> + <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())) : ?> + <?php if (isset($shipItems[$_item->getId()])) : ?> + <?= (float)$shipItems[$_item->getId()]->getQty() * 1 ?> + <?php elseif ($_item->getIsVirtual()) : ?> + <?= $block->escapeHtml(__('N/A')) ?> + <?php else : ?> 0 <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr class="border"> <td class="col-product"> - <?php if ($block->getOrderOptions($_item->getOrderItem())): ?> + <?php if ($block->getOrderOptions($_item->getOrderItem())) : ?> <dl class="item-options"> - <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option): ?> - <dt><?= /* @escapeNotVerified */ $option['label'] ?></dt> + <?php foreach ($block->getOrderOptions($_item->getOrderItem()) as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <dd> - <?php if (isset($option['custom_view']) && $option['custom_view']): ?> - <?= /* @escapeNotVerified */ $option['value'] ?> - <?php else: ?> - <?= $block->truncateString($option['value'], 55, '', $_remainder) ?> - <?php if ($_remainder):?> - ... <span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_remainder ?></span> + <?php if (isset($option['custom_view']) && $option['custom_view']) : ?> + <?= $block->escapeHtml($option['value']) ?> + <?php else : ?> + <?= $block->escapeHtml($block->truncateString($option['value'], 55, '', $_remainder)) ?> + <?php if ($_remainder) :?> + ... <span id="<?= $block->escapeHtmlAttr($_id = 'id' . uniqid()) ?>"><?= $block->escapeHtml($_remainder) ?></span> <script> -require(['prototype'], function(){ - - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - -}); -</script> + require(['prototype'], function(){ + <?php $escapedId = $block->escapeJs($_id) ?> + $('<?= /* @noEscape */ $escapedId ?>').hide(); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $escapedId ?>').show();}); + $('<?= /* @noEscape */ $escapedId ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $escapedId ?>').hide();}); + }); + </script> <?php endif;?> <?php endif;?> </dd> diff --git a/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml b/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml index 5955d0337bb6e..26264cc2cc87f 100644 --- a/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/Bundle/view/base/templates/product/price/final_price.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -21,76 +18,63 @@ $maximalPrice = $finalPriceModel->getMaximalPrice(); $regularPriceModel = $block->getPriceType('regular_price'); $maximalRegularPrice = $regularPriceModel->getMaximalPrice(); $minimalRegularPrice = $regularPriceModel->getMinimalPrice(); +$regularPriceAttributes = [ + 'display_label' => __('Regular Price'), + 'price_id' => $block->getPriceId('old-price-' . $idSuffix), + 'include_container' => true, + 'skip_adjustments' => true +]; +$renderMinimalRegularPrice = $block->renderAmount($minimalRegularPrice, $regularPriceAttributes); ?> -<?php if ($block->getSaleableItem()->getPriceView()): ?> +<?php if ($block->getSaleableItem()->getPriceView()) : ?> <p class="minimal-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalPrice, [ + <?= /* @noEscape */ $block->renderAmount($minimalPrice, [ 'display_label' => __('As low as'), 'price_id' => $block->getPriceId('from-'), 'include_container' => true ]); ?> - <?php if ($minimalPrice < $minimalRegularPrice): ?> + <?php if ($minimalPrice < $minimalRegularPrice) : ?> <span class="old-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalRegularPrice, [ - 'display_label' => __('Regular Price'), - 'price_id' => $block->getPriceId('old-price-' . $idSuffix), - 'include_container' => true, - 'skip_adjustments' => true - ]); ?> + <?= /* @noEscape */ $renderMinimalRegularPrice ?> </span> <?php endif ?> </p> -<?php else: ?> - <?php if ($block->showRangePrice()): ?> +<?php else : ?> + <?php if ($block->showRangePrice()) : ?> <p class="price-from"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalPrice, [ + <?= /* @noEscape */ $block->renderAmount($minimalPrice, [ 'display_label' => __('From'), 'price_id' => $block->getPriceId('from-'), 'price_type' => 'minPrice', 'include_container' => true ]); ?> - <?php if ($minimalPrice < $minimalRegularPrice): ?> + <?php if ($minimalPrice < $minimalRegularPrice) : ?> <span class="old-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalRegularPrice, [ - 'display_label' => __('Regular Price'), - 'price_id' => $block->getPriceId('old-price-' . $idSuffix), - 'include_container' => true, - 'skip_adjustments' => true - ]); ?> + <?= /* @noEscape */ $renderMinimalRegularPrice ?> </span> <?php endif ?> </p> <p class="price-to"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($maximalPrice, [ + <?= /* @noEscape */ $block->renderAmount($maximalPrice, [ 'display_label' => __('To'), 'price_id' => $block->getPriceId('to-'), 'price_type' => 'maxPrice', 'include_container' => true ]); ?> - <?php if ($maximalPrice < $maximalRegularPrice): ?> + <?php if ($maximalPrice < $maximalRegularPrice) : ?> <span class="old-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($maximalRegularPrice, [ - 'display_label' => __('Regular Price'), - 'price_id' => $block->getPriceId('old-price-' . $idSuffix), - 'include_container' => true, - 'skip_adjustments' => true - ]); ?> + <?= /* @noEscape */ $block->renderAmount($maximalRegularPrice, $regularPriceAttributes); ?> </span> <?php endif ?> </p> - <?php else: ?> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalPrice, [ + <?php else : ?> + <?= /* @noEscape */ $block->renderAmount($minimalPrice, [ 'price_id' => $block->getPriceId('product-price-'), 'include_container' => true ]); ?> - <?php if ($minimalPrice < $minimalRegularPrice): ?> + <?php if ($minimalPrice < $minimalRegularPrice) : ?> <span class="old-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($minimalRegularPrice, [ - 'display_label' => __('Regular Price'), - 'price_id' => $block->getPriceId('old-price-' . $idSuffix), - 'include_container' => true, - 'skip_adjustments' => true - ]); ?> + <?= /* @noEscape */ $renderMinimalRegularPrice ?> </span> <?php endif ?> <?php endif ?> diff --git a/app/code/Magento/Bundle/view/base/templates/product/price/selection/amount.phtml b/app/code/Magento/Bundle/view/base/templates/product/price/selection/amount.phtml index 53d24dd7c2c07..00bd9955632e5 100644 --- a/app/code/Magento/Bundle/view/base/templates/product/price/selection/amount.phtml +++ b/app/code/Magento/Bundle/view/base/templates/product/price/selection/amount.phtml @@ -3,11 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var \Magento\Framework\Pricing\Render\Amount $block */ ?> -<?= /* @escapeNotVerified */ $block->formatCurrency($block->getDisplayValue(), (bool) $block->getIncludeContainer()) ?> +<?= /* @noEscape */ $block->formatCurrency($block->getDisplayValue(), (bool) $block->getIncludeContainer()) ?> diff --git a/app/code/Magento/Bundle/view/base/templates/product/price/tier_prices.phtml b/app/code/Magento/Bundle/view/base/templates/product/price/tier_prices.phtml index 5f152c4bbefbc..f5f67588a1c49 100644 --- a/app/code/Magento/Bundle/view/base/templates/product/price/tier_prices.phtml +++ b/app/code/Magento/Bundle/view/base/templates/product/price/tier_prices.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -16,10 +13,10 @@ $tierPriceModel = $block->getPrice(); $tierPrices = $tierPriceModel->getTierPriceList(); ?> <?php if (count($tierPrices)) : ?> - <ul class="<?= /* @escapeNotVerified */ ($block->hasListClass() ? $block->getListClass() : 'prices-tier items') ?>"> + <ul class="<?= $block->escapeHtmlAttr(($block->hasListClass() ? $block->getListClass() : 'prices-tier items')) ?>"> <?php foreach ($tierPrices as $index => $price) : ?> <li class="item"> - <?php /* @escapeNotVerified */ echo __( + <?= /* @noEscape */ __( 'Buy %1 with %2 discount each', $price['price_qty'], '<strong class="benefit">' . round($price['percentage_value']) . '%</strong>' diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml index 31a39c1cd162c..ba58544c26af5 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/backbutton.phtml @@ -6,5 +6,5 @@ ?> <button type="button" class="action back customization"> - <span><?= /* @escapeNotVerified */ __('Go back to product details') ?></span> + <span><?= $block->escapeHtml(__('Go back to product details')) ?></span> </button> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml index d7aea4237b100..480ffea5bc8b3 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/customize.phtml @@ -3,18 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_product = $block->getProduct() ?> -<?php if ($_product->isSaleable() && $block->hasOptions()):?> +<?php if ($_product->isSaleable() && $block->hasOptions()) :?> <div class="bundle-actions"> <button id="bundle-slide" class="action primary customize" type="button"> - <span><?= /* @escapeNotVerified */ __('Customize and Add to Cart') ?></span> + <span><?= $block->escapeHtml(__('Customize and Add to Cart')) ?></span> </button> </div> <?php endif;?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/options/notice.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/options/notice.phtml index 2dbea0fd21395..927b64352d591 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/options/notice.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/options/notice.phtml @@ -4,4 +4,4 @@ * See COPYING.txt for license details. */ ?> -<p class="required"><?= /* @escapeNotVerified */ __('* Required Fields') ?></p> +<p class="required"><?= $block->escapeHtml(__('* Required Fields')) ?></p> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml index bc4337fa7ae24..9bf179e622f17 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/summary.phtml @@ -3,40 +3,37 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_product = $block->getProduct(); ?> -<?php if ($_product->isSaleable() && $block->hasOptions()): ?> +<?php if ($_product->isSaleable() && $block->hasOptions()) : ?> <div id="bundleSummary" class="block-bundle-summary" data-mage-init='{"sticky":{"container": ".product-add-form"}}'> <div class="title"> - <strong><?= /* @escapeNotVerified */ __('Your Customization') ?></strong> + <strong><?= $block->escapeHtml(__('Your Customization')) ?></strong> </div> <div class="content"> <div class="bundle-info"> <?= $block->getImage($_product, 'bundled_product_customization_page')->toHtml() ?> <div class="product-details"> <strong class="product name"><?= $block->escapeHtml($_product->getName()) ?></strong> - <?php if ($_product->getIsSalable()): ?> - <p class="available stock" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('In stock') ?></span> + <?php if ($_product->getIsSalable()) : ?> + <p class="available stock" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('In stock')) ?></span> </p> - <?php else: ?> - <p class="unavailable stock" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('Out of stock') ?></span> + <?php else : ?> + <p class="unavailable stock" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('Out of stock')) ?></span> </p> <?php endif; ?> <?= $block->getChildHtml('', true) ?> </div> </div> <div class="bundle-summary"> - <strong class="subtitle"><?= /* @escapeNotVerified */ __('Summary') ?></strong> + <strong class="subtitle"><?= $block->escapeHtml(__('Summary')) ?></strong> <div id="bundle-summary" data-container="product-summary"> <ul data-mage-init='{"productSummary": []}' class="bundle items"></ul> <script data-template="bundle-summary" type="text/x-magento-template"> @@ -46,7 +43,7 @@ </li> </script> <script data-template="bundle-option" type="text/x-magento-template"> - <div><?= /* @escapeNotVerified */ __('%1 x %2', '<%- data._quantity_ %>', '<%- data._label_ %>') ?></div> + <div><?= /* @noEscape */ __('%1 x %2', '<%- data._quantity_ %>', '<%- data._label_ %>') ?></div> </script> </div> </div> @@ -61,7 +58,7 @@ "slideBackSelector": ".action.customization.back", "bundleProductSelector": "#bundleProduct", "bundleOptionsContainer": ".product-add-form" - <?php if ($block->isStartCustomization()): ?> + <?php if ($block->isStartCustomization()) : ?> ,"autostart": true <?php endif;?> } diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle.phtml index ce9ef89a82bd1..ee29fc61d0145 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle.phtml @@ -4,19 +4,17 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle */ ?> <?php $_product = $block->getProduct() ?> -<?php if ($block->displayProductStockStatus()): ?> - <?php if ($_product->isAvailable()): ?> - <p class="stock available" title="<?= /* @escapeNotVerified */ __('Availability:') ?>"> - <span><?= /* @escapeNotVerified */ __('In stock') ?></span> +<?php if ($block->displayProductStockStatus()) : ?> + <?php if ($_product->isAvailable()) : ?> + <p class="stock available" title="<?= $block->escapeHtmlAttr(__('Availability:')) ?>"> + <span><?= $block->escapeHtml(__('In stock')) ?></span> </p> - <?php else: ?> - <p class="stock unavailable" title="<?= /* @escapeNotVerified */ __('Availability:') ?>"> - <span><?= /* @escapeNotVerified */ __('Out of stock') ?></span> + <?php else : ?> + <p class="stock unavailable" title="<?= $block->escapeHtmlAttr(__('Availability:')) ?>"> + <span><?= $block->escapeHtml(__('Out of stock')) ?></span> </p> <?php endif; ?> <?php endif; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml index 830d03c826f32..5b56598dc58e2 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Checkbox */ ?> @@ -17,34 +14,34 @@ </label> <div class="control"> <div class="nested options-list"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" - class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> product bundle option" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>"/> - <?php else:?> - <?php foreach($_selections as $_selection): ?> + class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> product bundle option" + name="bundle_option[<?= $block->escapeHtml($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>"/> + <?php else :?> + <?php foreach ($_selections as $_selection) : ?> <div class="field choice"> - <input class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> checkbox product bundle option change-container-classname" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + <input class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> checkbox product bundle option change-container-classname" + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" type="checkbox" - <?php if ($_option->getRequired()) /* @escapeNotVerified */ echo 'data-validate="{\'validate-one-required-by-name\':\'input[name^="bundle_option[' . $_option->getId() . ']"]:checked\'}"'?> - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][<?= /* @escapeNotVerified */ $_selection->getId() ?>]" - data-selector="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][<?= /* @escapeNotVerified */ $_selection->getId() ?>]" - <?php if ($block->isSelected($_selection)) echo ' checked="checked"' ?> - <?php if (!$_selection->isSaleable()) echo ' disabled="disabled"' ?> - value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"/> + <?php if ($_option->getRequired()) { echo 'data-validate="{\'validate-one-required-by-name\':\'input[name^="bundle_option[' . $block->escapeHtmlAttr($_option->getId()) . ']"]:checked\'}"'; } ?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][<?= $block->escapeHtmlAttr($_selection->getId()) ?>]" + data-selector="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][<?= $block->escapeHtmlAttr($_selection->getId()) ?>]" + <?php if ($block->isSelected($_selection)) { echo ' checked="checked"'; } ?> + <?php if (!$_selection->isSaleable()) { echo ' disabled="disabled"'; } ?> + value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"/> <label class="label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"> - <span><?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection) ?></span> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"> + <span><?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selection) ?></span> <br/> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> </label> </div> <?php endforeach; ?> - <div id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></div> + <div id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></div> <?php endif; ?> </div> </div> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml index 718d43070a5fd..d6f9fdb74ef62 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/multi.phtml @@ -3,39 +3,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Multi */ ?> <?php $_option = $block->getOption() ?> <?php $_selections = $_option->getSelections() ?> <div class="field option <?= ($_option->getRequired()) ? ' required': '' ?>"> - <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>"> + <label class="label" for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> </label> <div class="control"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> <input type="hidden" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>"/> - <?php else: ?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>"/> + <?php else : ?> <select multiple="multiple" size="5" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][]" - data-selector="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>][]" - class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> multiselect product bundle option change-container-classname" - <?php if ($_option->getRequired()) echo 'data-validate={required:true}' ?>> - <?php if(!$_option->getRequired()): ?> - <option value=""><?= /* @escapeNotVerified */ __('None') ?></option> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][]" + data-selector="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>][]" + class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> multiselect product bundle option change-container-classname" + <?php if ($_option->getRequired()) { echo 'data-validate={required:true}'; } ?>> + <?php if (!$_option->getRequired()) : ?> + <option value=""><?= $block->escapeHtml(__('None')) ?></option> <?php endif; ?> - <?php foreach ($_selections as $_selection): ?> - <option value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" - <?php if ($block->isSelected($_selection)) echo ' selected="selected"' ?> - <?php if (!$_selection->isSaleable()) echo ' disabled="disabled"' ?>> - <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection, false) ?> + <?php foreach ($_selections as $_selection) : ?> + <option value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + <?php if ($block->isSelected($_selection)) { echo ' selected="selected"'; } ?> + <?php if (!$_selection->isSaleable()) { echo ' disabled="disabled"'; } ?>> + <?= /* @noEscape */ $block->getSelectionQtyTitlePrice($_selection, false) ?> </option> <?php endforeach; ?> </select> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml index 1f33d97227ea3..11ed226c176c8 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Radio */ ?> <?php $_option = $block->getOption(); ?> @@ -19,8 +16,8 @@ </label> <div class="control"> <div class="nested options-list"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selections[0]) ?> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" class="bundle-option-<?= (int)$_option->getId() ?> product bundle option" @@ -29,54 +26,54 @@ id="bundle-option-<?= (int)$_option->getId() ?>-<?= (int)$_selections[0]->getSelectionId() ?>" checked="checked" /> - <?php else:?> - <?php if (!$_option->getRequired()): ?> + <?php else :?> + <?php if (!$_option->getRequired()) : ?> <div class="field choice"> <input type="radio" class="radio product bundle option" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" <?= ($_default && $_default->isSalable())?'':' checked="checked" ' ?> value=""/> - <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>"> - <span><?= /* @escapeNotVerified */ __('None') ?></span> + <label class="label" for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>"> + <span><?= $block->escapeHtml(__('None')) ?></span> </label> </div> <?php endif; ?> - <?php foreach ($_selections as $_selection): ?> + <?php foreach ($_selections as $_selection) : ?> <div class="field choice"> <input type="radio" class="radio product bundle option change-container-classname" - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" - <?php if ($_option->getRequired()) echo 'data-validate="{\'validate-one-required-by-name\':true}"'?> - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - <?php if ($block->isSelected($_selection)) echo ' checked="checked"' ?> - <?php if (!$_selection->isSaleable()) echo ' disabled="disabled"' ?> - value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"/> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + <?php if ($_option->getRequired()) { echo 'data-validate="{\'validate-one-required-by-name\':true}"'; }?> + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + <?php if ($block->isSelected($_selection)) { echo ' checked="checked"'; } ?> + <?php if (!$_selection->isSaleable()) { echo ' disabled="disabled"'; } ?> + value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"/> <label class="label" - for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"> - <span><?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection) ?></span> + for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>"> + <span><?= /* @noEscape */ $block->getSelectionTitlePrice($_selection) ?></span> <br/> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> </label> </div> <?php endforeach; ?> - <div id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></div> + <div id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></div> <?php endif; ?> <div class="field qty qty-holder"> - <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input"> - <span><?= /* @escapeNotVerified */ __('Quantity') ?></span> + <label class="label" for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input"> + <span><?= $block->escapeHtml(__('Quantity')) ?></span> </label> <div class="control"> - <input <?php if (!$_canChangeQty) echo ' disabled="disabled"' ?> - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input" - class="input-text qty<?php if (!$_canChangeQty) echo ' qty-disabled' ?>" + <input <?php if (!$_canChangeQty) { echo ' disabled="disabled"'; } ?> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input" + class="input-text qty<?php if (!$_canChangeQty) { echo ' qty-disabled'; } ?>" type="number" - name="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_defaultQty ?>"/> + name="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_defaultQty) ?>"/> </div> </div> </div> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml index 4ea00f62b2043..1edf45fe8ca99 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Select */ ?> @@ -14,36 +11,36 @@ <?php $_default = $_option->getDefaultSelection(); ?> <?php list($_defaultQty, $_canChangeQty) = $block->getDefaultValues(); ?> <div class="field option <?= ($_option->getRequired()) ? ' required': '' ?>"> - <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>"> + <label class="label" for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> </label> <div class="control"> - <?php if ($block->showSingle()): ?> - <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> + <?php if ($block->showSingle()) : ?> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selections[0]) ?> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" - class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> product bundle option" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_selections[0]->getSelectionId() ?>"/> - <?php else:?> - <select id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>" - name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> product bundle option bundle-option-select change-container-classname" - <?php if ($_option->getRequired()) echo 'data-validate = {required:true}' ?>> - <option value=""><?= /* @escapeNotVerified */ __('Choose a selection...') ?></option> - <?php foreach ($_selections as $_selection): ?> - <option value="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" - <?php if ($block->isSelected($_selection)) echo ' selected="selected"' ?> - <?php if (!$_selection->isSaleable()) echo ' disabled="disabled"' ?>> - <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection, false) ?> + class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> product bundle option" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_selections[0]->getSelectionId()) ?>"/> + <?php else :?> + <select id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>" + name="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + class="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?> product bundle option bundle-option-select change-container-classname" + <?php if ($_option->getRequired()) { echo 'data-validate = {required:true}'; } ?>> + <option value=""><?= $block->escapeHtml(__('Choose a selection...')) ?></option> + <?php foreach ($_selections as $_selection) : ?> + <option value="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" + <?php if ($block->isSelected($_selection)) { echo ' selected="selected"'; } ?> + <?php if (!$_selection->isSaleable()) { echo ' disabled="disabled"'; } ?>> + <?= /* @noEscape */ $block->getSelectionTitlePrice($_selection, false) ?> </option> <?php endforeach; ?> </select> - <div id="option-tier-prices-<?= /* @escapeNotVerified */ $_option->getId() ?>" class="option-tier-prices"> - <?php foreach ($_selections as $_selection): ?> + <div id="option-tier-prices-<?= $block->escapeHtmlAttr($_option->getId()) ?>" class="option-tier-prices"> + <?php foreach ($_selections as $_selection) : ?> <div data-role="selection-tier-prices" - data-selection-id="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + data-selection-id="<?= $block->escapeHtmlAttr($_selection->getSelectionId()) ?>" class="selection-tier-prices"> <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> </div> @@ -52,17 +49,17 @@ <?php endif; ?> <div class="nested"> <div class="field qty qty-holder"> - <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input"> - <span><?= /* @escapeNotVerified */ __('Quantity') ?></span> + <label class="label" for="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input"> + <span><?= $block->escapeHtml(__('Quantity')) ?></span> </label> <div class="control"> - <input <?php if (!$_canChangeQty) echo ' disabled="disabled"' ?> - id="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-qty-input" - class="input-text qty<?php if (!$_canChangeQty) echo ' qty-disabled' ?>" + <input <?php if (!$_canChangeQty) { echo ' disabled="disabled"'; } ?> + id="bundle-option-<?= $block->escapeHtmlAttr($_option->getId()) ?>-qty-input" + class="input-text qty<?php if (!$_canChangeQty) { echo ' qty-disabled'; } ?>" type="number" - name="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="bundle_option_qty[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - value="<?= /* @escapeNotVerified */ $_defaultQty ?>"/> + name="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option_qty[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_defaultQty) ?>"/> </div> </div> </div> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml index 157e2d959720b..ec425ca7711ae 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml @@ -3,24 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block Magento\Bundle\Block\Catalog\Product\View\Type\Bundle */ ?> <?php $product = $block->getProduct(); -$helper = $this->helper('Magento\Catalog\Helper\Output'); +$helper = $this->helper(Magento\Catalog\Helper\Output::class); $stripSelection = $product->getConfigureMode() ? true : false; $options = $block->decorateArray($block->getOptions($stripSelection)); ?> -<?php if ($product->isSaleable()):?> - <?php if (count($options)): ?> +<?php if ($product->isSaleable()) :?> + <?php if (count($options)) : ?> <script type="text/x-magento-init"> { "#product_addtocart_form": { "priceBundle": { - "optionConfig": <?= /* @escapeNotVerified */ $block->getJsonConfig() ?>, + "optionConfig": <?= /* @noEscape */ $block->getJsonConfig() ?>, "controlContainer": ".field.option" } } @@ -28,17 +26,20 @@ $options = $block->decorateArray($block->getOptions($stripSelection)); </script> <fieldset class="fieldset fieldset-bundle-options"> <legend id="customizeTitle" class="legend title"> - <span><?= /* @escapeNotVerified */ __('Customize %1', $helper->productAttribute($product, $product->getName(), 'name')) ?></span> + <span><?= $block->escapeHtml(__('Customize %1', $helper->productAttribute($product, $product->getName(), 'name'))) ?></span> </legend><br /> <?= $block->getChildHtml('product_info_bundle_options_top') ?> - <?php foreach ($options as $option): ?> - <?php if (!$option->getSelections()): ?> - <?php continue; ?> - <?php endif; ?> - <?= $block->getOptionHtml($option) ?> + <?php foreach ($options as $option) : ?> + <?php + if (!$option->getSelections()) { + continue; + } else { + echo $block->getOptionHtml($option); + } + ?> <?php endforeach; ?> </fieldset> - <?php else: ?> - <p class="empty"><?= /* @escapeNotVerified */ __('No options of this product are available.') ?></p> + <?php else : ?> + <p class="empty"><?= $block->escapeHtml(__('No options of this product are available.')) ?></p> <?php endif; ?> <?php endif;?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/creditmemo/default.phtml b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/creditmemo/default.phtml index a87c2167e66d4..cbf7755ae9912 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/creditmemo/default.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/creditmemo/default.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> <?php $parentItem = $block->getItem() ?> @@ -13,15 +11,15 @@ <?php $items = $block->getChildren($parentItem) ?> -<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> +<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php // As part of invoice item renderer logic, the order is set on each invoice item. @@ -30,40 +28,40 @@ $_item->setOrder($_order); ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="bundle-option-label"> <td colspan="3"> - <strong><?= /* @escapeNotVerified */ $attributes['option_label'] ?></strong> + <strong><?= $block->escapeHtml($attributes['option_label']) ?></strong> </td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <tr class="bundle-item bundle-parent"> <td class="item-info"> <p class="product-name"><?= $block->escapeHtml($_item->getName()) ?></p> - <p class="sku"><?= /* @escapeNotVerified */ __('SKU') ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> + <p class="sku"><?= $block->escapeHtml(__('SKU')) ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> </td> - <?php else: ?> + <?php else : ?> <tr class="bundle-item bundle-option-value"> <td class="item-info"> <p><?= $block->getValueHtml($_item) ?></p> </td> <?php endif; ?> <td class="item-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty() * 1 ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> <td class="item-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->getItemPrice($_item) ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->getItemPrice($_item) ?> + <?php else : ?>   <?php endif; ?> </td> @@ -71,14 +69,14 @@ <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr> <td colspan="3" class="item-extra"> - <?php if ($block->getItemOptions()): ?> + <?php if ($block->getItemOptions()) : ?> <dl> - <?php foreach ($block->getItemOptions() as $option): ?> - <dt><strong><em><?= /* @escapeNotVerified */ $option['label'] ?></em></strong></dt> - <dd><?= /* @escapeNotVerified */ $option['value'] ?></dd> + <?php foreach ($block->getItemOptions() as $option) : ?> + <dt><strong><em><?= $block->escapeHtml($option['label']) ?></em></strong></dt> + <dd><?= $block->escapeHtml($option['value']) ?></dd> <?php endforeach; ?> </dl> <?php endif; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/invoice/default.phtml b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/invoice/default.phtml index ee79ca3b0a7a5..d24fa390fa041 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/invoice/default.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/invoice/default.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> @@ -14,15 +12,15 @@ <?php $_index = 0 ?> <?php $_order = $block->getItem()->getOrder(); ?> -<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> +<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> <?php // As part of invoice item renderer logic, the order is set on each invoice item. @@ -31,40 +29,40 @@ $_item->setOrder($_order); ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="bundle-option-label"> <td colspan="3"> - <strong><em><?= /* @escapeNotVerified */ $attributes['option_label'] ?></em></strong> + <strong><em><?= $block->escapeHtml($attributes['option_label']) ?></em></strong> </td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> <tr class="bundle-item bundle-parent"> <td class="item-info"> <p class="product-name"><?= $block->escapeHtml($_item->getName()) ?></p> - <p class="sku"><?= /* @escapeNotVerified */ __('SKU') ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> + <p class="sku"><?= $block->escapeHtml(__('SKU')) ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> </td> - <?php else: ?> + <?php else : ?> <tr class="bundle-item bundle-option-value"> <td class="item-info"> <p><?= $block->getValueHtml($_item) ?></p> </td> <?php endif; ?> <td class="item-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty() * 1 ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> <td class="item-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->getItemPrice($_item) ?> - <?php else: ?> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= /* @noEscape */ $block->getItemPrice($_item) ?> + <?php else : ?>   <?php endif; ?> </td> @@ -72,14 +70,14 @@ <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr> <td colspan="3" class="item-extra"> - <?php if ($block->getItemOptions()): ?> + <?php if ($block->getItemOptions()) : ?> <dl> - <?php foreach ($block->getItemOptions() as $option): ?> - <dt><strong><em><?= /* @escapeNotVerified */ $option['label'] ?></em></strong></dt> - <dd><?= /* @escapeNotVerified */ $option['value'] ?></dd> + <?php foreach ($block->getItemOptions() as $option) : ?> + <dt><strong><em><?= $block->escapeHtml($option['label']) ?></em></strong></dt> + <dd><?= $block->escapeHtml($option['value']) ?></dd> <?php endforeach; ?> </dl> <?php endif; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/order/default.phtml b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/order/default.phtml index c9c3103c448e4..94dc0917fcf8a 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/order/default.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/order/default.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> <?php $_item = $block->getItem() ?> @@ -14,41 +12,41 @@ <?php $parentItem = $block->getItem() ?> <?php $items = array_merge([$parentItem], $parentItem->getChildrenItems()); ?> -<?php if ($block->getItemOptions() || $_item->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $_item) && $_item->getGiftMessageId()): ?> +<?php if ($block->getItemOptions() || $_item->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $_item) && $_item->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> - <?php if ($_item->getParentItem()): ?> + <?php if ($_item->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="bundle-option-label"> <td colspan="3"> - <strong><em><?= /* @escapeNotVerified */ $attributes['option_label'] ?></em></strong> + <strong><em><?= $block->escapeHtml($attributes['option_label']) ?></em></strong> </td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <?php if (!$_item->getParentItem()): ?> + <?php if (!$_item->getParentItem()) : ?> <tr class="bundle-item bundle-parent"> <td class="item-info"> <p class="product-name"><?= $block->escapeHtml($_item->getName()) ?></p> - <p class="sku"><?= /* @escapeNotVerified */ __('SKU') ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> + <p class="sku"><?= $block->escapeHtml(__('SKU')) ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> </td> <td class="item-qty"> - <?= /* @escapeNotVerified */ $_item->getQtyOrdered() * 1 ?> + <?= (float)$_item->getQtyOrdered() * 1 ?> </td> <td class="item-price"> - <?= /* @escapeNotVerified */ $block->getItemPrice($_item) ?> + <?= /* @noEscape */ $block->getItemPrice($_item) ?> </td> </tr> - <?php else: ?> + <?php else : ?> <tr class="bundle-item bundle-option-value"> <td class="item-info" colspan="3"> <p><?= $block->getValueHtml($_item) ?></p> @@ -58,25 +56,25 @@ <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr> <td colspan="3" class="item-extra"> - <?php if ($block->getItemOptions()): ?> + <?php if ($block->getItemOptions()) : ?> <dl> - <?php foreach ($block->getItemOptions() as $option): ?> - <dt><strong><em><?= /* @escapeNotVerified */ $option['label'] ?></em></strong></dt> - <dd><?= /* @escapeNotVerified */ $option['value'] ?></dd> + <?php foreach ($block->getItemOptions() as $option) : ?> + <dt><strong><em><?= $block->escapeHtml($option['label']) ?></em></strong></dt> + <dd><?= $block->escapeHtml($option['value']) ?></dd> <?php endforeach; ?> </dl> <?php endif; ?> - <?php if ($_item->getGiftMessageId() && $_giftMessage = $this->helper('Magento\GiftMessage\Helper\Message')->getGiftMessage($_item->getGiftMessageId())): ?> + <?php if ($_item->getGiftMessageId() && $_giftMessage = $this->helper(Magento\GiftMessage\Helper\Message::class)->getGiftMessage($_item->getGiftMessageId())) : ?> <table class="message-gift"> <tr> <td> - <h3><?= /* @escapeNotVerified */ __('Gift Message') ?></h3> - <strong><?= /* @escapeNotVerified */ __('From:') ?></strong> <?= $block->escapeHtml($_giftMessage->getSender()) ?> - <br /><strong><?= /* @escapeNotVerified */ __('To:') ?></strong> <?= $block->escapeHtml($_giftMessage->getRecipient()) ?> - <br /><strong><?= /* @escapeNotVerified */ __('Message:') ?></strong> + <h3><?= $block->escapeHtml(__('Gift Message')) ?></h3> + <strong><?= $block->escapeHtml(__('From:')) ?></strong> <?= $block->escapeHtml($_giftMessage->getSender()) ?> + <br /><strong><?= $block->escapeHtml(__('To:')) ?></strong> <?= $block->escapeHtml($_giftMessage->getRecipient()) ?> + <br /><strong><?= $block->escapeHtml(__('Message:')) ?></strong> <br /><?= $block->escapeHtml($_giftMessage->getMessage()) ?> </td> </tr> diff --git a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/shipment/default.phtml b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/shipment/default.phtml index 61582087d1fcc..bf84ad33b9267 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/email/order/items/shipment/default.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/email/order/items/shipment/default.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> @@ -14,49 +12,49 @@ <?php $items = array_merge([$parentItem->getOrderItem()], $parentItem->getOrderItem()->getChildrenItems()) ?> <?php $shipItems = $block->getChildren($parentItem) ?> -<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> +<?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> -<?php else: ?> +<?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> - <?php if ($_item->getParentItem()): ?> + <?php if ($_item->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="bundle-option-label"> <td colspan="2"> - <strong><em><?= /* @escapeNotVerified */ $attributes['option_label'] ?></em></strong> + <strong><em><?= $block->escapeHtml($attributes['option_label']) ?></em></strong> </td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <?php if (!$_item->getParentItem()): ?> + <?php if (!$_item->getParentItem()) : ?> <tr class="bundle-item bundle-parent"> <td class="item-info"> <p class="product-name"><?= $block->escapeHtml($_item->getName()) ?></p> - <p class="sku"><?= /* @escapeNotVerified */ __('SKU') ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> + <p class="sku"><?= $block->escapeHtml(__('SKU')) ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p> </td> - <?php else: ?> + <?php else : ?> <tr class="bundle-item bundle-option-value"> <td class="item-info"> <p><?= $block->getValueHtml($_item) ?></p> </td> <?php endif; ?> <td class="item-qty"> - <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())): ?> - <?php if (isset($shipItems[$_item->getId()])): ?> - <?= /* @escapeNotVerified */ $shipItems[$_item->getId()]->getQty() * 1 ?> - <?php elseif ($_item->getIsVirtual()): ?> - <?= /* @escapeNotVerified */ __('N/A') ?> - <?php else: ?> + <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())) : ?> + <?php if (isset($shipItems[$_item->getId()])) : ?> + <?= (float)$shipItems[$_item->getId()]->getQty() * 1 ?> + <?php elseif ($_item->getIsVirtual()) : ?> + <?= $block->escapeHtml(__('N/A')) ?> + <?php else : ?> 0 <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> @@ -64,14 +62,14 @@ <?php endforeach; ?> -<?php if ($_showlastRow): ?> +<?php if ($_showlastRow) : ?> <tr> <td colspan="2" class="item-extra"> - <?php if ($block->getItemOptions()): ?> + <?php if ($block->getItemOptions()) : ?> <dl> - <?php foreach ($block->getItemOptions() as $option): ?> - <dt><strong><em><?= /* @escapeNotVerified */ $option['label'] ?></em></strong></dt> - <dd><?= /* @escapeNotVerified */ $option['value'] ?></dd> + <?php foreach ($block->getItemOptions() as $option) : ?> + <dt><strong><em><?= $block->escapeHtml($option['label']) ?></em></strong></dt> + <dd><?= $block->escapeHtml($option['value']) ?></dd> <?php endforeach; ?> </dl> <?php endif; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/js/components.phtml b/app/code/Magento/Bundle/view/frontend/templates/js/components.phtml index bad5acc209b5f..1bd7e554a73d6 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/js/components.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/js/components.phtml @@ -3,8 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml() ?> + diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml index b9d075966c5d1..64e6594b4f775 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> <?php $parentItem = $block->getItem() ?> @@ -16,91 +14,95 @@ <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> - <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> + <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> - <?php else: ?> + <?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="options-label"> - <td class="col label" colspan="7"><div class="option label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col label" colspan="7"><div class="option label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> -<tr id="order-item-row-<?= /* @escapeNotVerified */ $_item->getId() ?>" class="<?php if ($_item->getOrderItem()->getParentItem()): ?>item-options-container<?php else: ?>item-parent<?php endif; ?>"<?php if ($_item->getParentItem()): ?> data-th="<?= /* @escapeNotVerified */ $attributes['option_label'] ?>"<?php endif; ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td class="col name" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"> +<tr id="order-item-row-<?= $block->escapeHtmlAttr($_item->getId()) ?>" + class="<?php if ($_item->getOrderItem()->getParentItem()) : ?>item-options-container<?php else : ?>item-parent<?php endif; ?>" + <?php if ($_item->getParentItem()) : ?> + data-th="<?= $block->escapeHtmlAttr($attributes['option_label']) ?>" + <?php endif; ?>> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <td class="col name" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"> <strong class="product name product-item-name"><?= $block->escapeHtml($_item->getName()) ?></strong> </td> - <?php else: ?> - <td class="col value" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> + <?php else : ?> + <td class="col value" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> <?php endif; ?> - <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> - <td class="col price" data-th="<?= $block->escapeHtml(__('Price')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <td class="col sku" data-th="<?= $block->escapeHtmlAttr(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> + <td class="col price" data-th="<?= $block->escapeHtmlAttr(__('Price')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getItemPriceHtml($_item) ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Quantity')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> - <?php else: ?> + <td class="col qty" data-th="<?= $block->escapeHtmlAttr(__('Quantity')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <td class="col subtotal" data-th="<?= $block->escapeHtmlAttr(__('Subtotal')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getItemRowTotalHtml($_item) ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col discount" data-th="<?= $block->escapeHtml(__('Discount Amount')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $block->getOrder()->formatPrice(-$_item->getDiscountAmount()) ?> - <?php else: ?> + <td class="col discount" data-th="<?= $block->escapeHtmlAttr(__('Discount Amount')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= $block->escapeHtml($block->getOrder()->formatPrice(-$_item->getDiscountAmount()), ['span']) ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col rowtotal" data-th="<?= $block->escapeHtml(__('Row Total')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <td class="col rowtotal" data-th="<?= $block->escapeHtmlAttr(__('Row Total')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getItemRowTotalAfterDiscountHtml($_item) ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))): ?> +<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))) : ?> <tr> <td class="col options" colspan="7"> - <?php if ($_options = $block->getItemOptions()): ?> + <?php if ($_options = $block->getItemOptions()) : ?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <?php if (!$block->getPrintStatus()): ?> + <?php if (!$block->getPrintStatus()) : ?> <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> - <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="tooltip wrapper"<?php endif; ?>> - <?= /* @escapeNotVerified */ $_formatedOptionValue['value'] ?> - <?php if (isset($_formatedOptionValue['full_view'])): ?> + <dd<?php if (isset($_formatedOptionValue['full_view'])) : ?> class="tooltip wrapper"<?php endif; ?>> + <?= /* @noEscape */ $_formatedOptionValue['value'] ?> + <?php if (isset($_formatedOptionValue['full_view'])) : ?> <div class="tooltip content"> <dl class="item options"> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <dd><?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?></dd> + <dd><?= /* @noEscape */ $_formatedOptionValue['full_view'] ?></dd> </dl> </div> <?php endif; ?> </dd> - <?php else: ?> + <?php else : ?> <dd><?= $block->escapeHtml((isset($_option['print_value']) ? $_option['print_value'] : $_option['value'])) ?></dd> <?php endif; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml index e18d75ce77b9c..b2025e53986c3 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> <?php $parentItem = $block->getItem() ?> @@ -15,77 +13,85 @@ <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> - <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> + <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> - <?php else: ?> + <?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> - <?php if ($_item->getOrderItem()->getParentItem()): ?> + <?php if ($_item->getOrderItem()->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="options-label"> - <td class="col label" colspan="5"><div class="option label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td class="col label" colspan="5"> + <div class="option label"><?= $block->escapeHtml($attributes['option_label']) ?></div> + </td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <tr id="order-item-row-<?= /* @escapeNotVerified */ $_item->getId() ?>" class="<?php if ($_item->getOrderItem()->getParentItem()): ?>item-options-container<?php else: ?>item-parent<?php endif; ?>"<?php if ($_item->getOrderItem()->getParentItem()): ?> data-th="<?= /* @escapeNotVerified */ $attributes['option_label'] ?>"<?php endif; ?>> - <?php if (!$_item->getOrderItem()->getParentItem()): ?> - <td class="col name" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"> + <tr id="order-item-row-<?= $block->escapeHtmlAttr($_item->getId()) ?>" + class="<?php if ($_item->getOrderItem()->getParentItem()) : ?>item-options-container + <?php else : ?>item-parent + <?php endif; ?>" + <?php if ($_item->getOrderItem()->getParentItem()) : ?> + data-th="<?= $block->escapeHtmlAttr($attributes['option_label']) ?>" + <?php endif; ?>> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <td class="col name" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"> <strong class="product name product-item-name"><?= $block->escapeHtml($_item->getName()) ?></strong> </td> - <?php else: ?> - <td class="col value" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> + <?php else : ?> + <td class="col value" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> <?php endif; ?> - <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> - <td class="col price" data-th="<?= $block->escapeHtml(__('Price')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <td class="col sku" data-th="<?= $block->escapeHtmlAttr(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> + <td class="col price" data-th="<?= $block->escapeHtmlAttr(__('Price')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getItemPriceHtml($_item) ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty Invoiced')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> - <?= /* @escapeNotVerified */ $_item->getQty()*1 ?> - <?php else: ?> + <td class="col qty" data-th="<?= $block->escapeHtmlAttr(__('Qty Invoiced')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> + <?= (float)$_item->getQty() * 1 ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <td class="col subtotal" data-th="<?= $block->escapeHtmlAttr(__('Subtotal')) ?>"> + <?php if ($block->canShowPriceInfo($_item)) : ?> <?= $block->getItemRowTotalHtml($_item) ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))): ?> +<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))) : ?> <tr> <td class="col options" colspan="5"> - <?php if ($_options = $block->getItemOptions()): ?> + <?php if ($_options = $block->getItemOptions()) : ?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <?php if (!$block->getPrintStatus()): ?> + <?php if (!$block->getPrintStatus()) : ?> <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> - <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="tooltip wrapper"<?php endif; ?>> - <?= /* @escapeNotVerified */ $_formatedOptionValue['value'] ?> - <?php if (isset($_formatedOptionValue['full_view'])): ?> + <dd<?php if (isset($_formatedOptionValue['full_view'])) : ?> class="tooltip wrapper"<?php endif; ?>> + <?= /* @noEscape */ $_formatedOptionValue['value'] ?> + <?php if (isset($_formatedOptionValue['full_view'])) : ?> <div class="tooltip content"> <dl class="item options"> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <dd><?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?></dd> + <dd><?= /* @noEscape */ $_formatedOptionValue['full_view'] ?></dd> </dl> </div> <?php endif; ?> </dd> - <?php else: ?> + <?php else : ?> <dd><?= $block->escapeHtml((isset($_option['print_value']) ? $_option['print_value'] : $_option['value'])) ?></dd> <?php endif; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml index 74e1c5f874954..3507901dff861 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ $parentItem = $block->getItem(); $items = array_merge([$parentItem], $parentItem->getChildrenItems()); @@ -13,20 +11,20 @@ $index = 0; $prevOptionId = ''; ?> -<?php foreach ($items as $item): ?> +<?php foreach ($items as $item) : ?> <?php if ($block->getItemOptions() || $parentItem->getDescription() - || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) - && $parentItem->getGiftMessageId()): ?> + || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) + && $parentItem->getGiftMessageId()) : ?> <?php $showLastRow = true; ?> - <?php else: ?> + <?php else : ?> <?php $showLastRow = false; ?> <?php endif; ?> - <?php if ($item->getParentItem()): ?> + <?php if ($item->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($item) ?> - <?php if ($prevOptionId != $attributes['option_id']): ?> + <?php if ($prevOptionId != $attributes['option_id']) : ?> <tr class="options-label"> <td class="col label" colspan="5"><?= $block->escapeHtml($attributes['option_label']); ?></td> </tr> @@ -34,105 +32,103 @@ $prevOptionId = ''; <?php endif; ?> <?php endif; ?> <tr id="order-item-row-<?= /* @noEscape */ $item->getId() ?>" - class="<?php if ($item->getParentItem()): ?> + class="<?php if ($item->getParentItem()) : ?> item-options-container - <?php else: ?> + <?php else : ?> item-parent <?php endif; ?>" - <?php if ($item->getParentItem()): ?> - data-th="<?= $block->escapeHtml($attributes['option_label']); ?>" + <?php if ($item->getParentItem()) : ?> + data-th="<?= $block->escapeHtmlAttr($attributes['option_label']); ?>" <?php endif; ?>> - <?php if (!$item->getParentItem()): ?> - <td class="col name" data-th="<?= $block->escapeHtml(__('Product Name')); ?>"> + <?php if (!$item->getParentItem()) : ?> + <td class="col name" data-th="<?= $block->escapeHtmlAttr(__('Product Name')); ?>"> <strong class="product name product-item-name"><?= $block->escapeHtml($item->getName()); ?></strong> </td> - <?php else: ?> - <td class="col value" data-th="<?= $block->escapeHtml(__('Product Name')); ?>"> + <?php else : ?> + <td class="col value" data-th="<?= $block->escapeHtmlAttr(__('Product Name')); ?>"> <?= $block->getValueHtml($item); ?> </td> <?php endif; ?> - <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')); ?>"> + <td class="col sku" data-th="<?= $block->escapeHtmlAttr(__('SKU')); ?>"> <?= /* @noEscape */ $block->prepareSku($item->getSku()); ?> </td> - <td class="col price" data-th="<?= $block->escapeHtml(__('Price')); ?>"> - <?php if (!$item->getParentItem()): ?> + <td class="col price" data-th="<?= $block->escapeHtmlAttr(__('Price')); ?>"> + <?php if (!$item->getParentItem()) : ?> <?= /* @noEscape */ $block->getItemPriceHtml(); ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Quantity')); ?>"> - <?php if ( - ($item->getParentItem() && $block->isChildCalculated()) || + <td class="col qty" data-th="<?= $block->escapeHtmlAttr(__('Quantity')); ?>"> + <?php if (($item->getParentItem() && $block->isChildCalculated()) || (!$item->getParentItem() && !$block->isChildCalculated()) || - ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())): ?> + ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())) : ?> <ul class="items-qty"> <?php endif; ?> <?php if (($item->getParentItem() && $block->isChildCalculated()) || - (!$item->getParentItem() && !$block->isChildCalculated())): ?> - <?php if ($item->getQtyOrdered() > 0): ?> + (!$item->getParentItem() && !$block->isChildCalculated())) : ?> + <?php if ($item->getQtyOrdered() > 0) : ?> <li class="item"> <span class="title"><?= $block->escapeHtml(__('Ordered')); ?></span> <span class="content"><?= /* @noEscape */ $item->getQtyOrdered() * 1; ?></span> </li> <?php endif; ?> - <?php if ($item->getQtyShipped() > 0 && !$block->isShipmentSeparately()): ?> + <?php if ($item->getQtyShipped() > 0 && !$block->isShipmentSeparately()) : ?> <li class="item"> <span class="title"><?= $block->escapeHtml(__('Shipped')); ?></span> <span class="content"><?= /* @noEscape */ $item->getQtyShipped() * 1; ?></span> </li> <?php endif; ?> - <?php if ($item->getQtyCanceled() > 0): ?> + <?php if ($item->getQtyCanceled() > 0) : ?> <li class="item"> <span class="title"><?= $block->escapeHtml(__('Canceled')); ?></span> <span class="content"><?= /* @noEscape */ $item->getQtyCanceled() * 1; ?></span> </li> <?php endif; ?> - <?php if ($item->getQtyRefunded() > 0): ?> + <?php if ($item->getQtyRefunded() > 0) : ?> <li class="item"> <span class="title"><?= $block->escapeHtml(__('Refunded')); ?></span> <span class="content"><?= /* @noEscape */ $item->getQtyRefunded() * 1; ?></span> </li> <?php endif; ?> - <?php elseif ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately()): ?> + <?php elseif ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately()) : ?> <li class="item"> <span class="title"><?= $block->escapeHtml(__('Shipped')); ?></span> <span class="content"><?= /* @noEscape */ $item->getQtyShipped() * 1; ?></span> </li> - <?php else: ?> + <?php else : ?> <span class="content"><?= /* @noEscape */ $parentItem->getQtyOrdered() * 1; ?></span> <?php endif; ?> - <?php if ( - ($item->getParentItem() && $block->isChildCalculated()) || + <?php if (($item->getParentItem() && $block->isChildCalculated()) || (!$item->getParentItem() && !$block->isChildCalculated()) || - ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())):?> + ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())) :?> </ul> <?php endif; ?> </td> - <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if (!$item->getParentItem()): ?> + <td class="col subtotal" data-th="<?= $block->escapeHtmlAttr(__('Subtotal')) ?>"> + <?php if (!$item->getParentItem()) : ?> <?= /* @noEscape */ $block->getItemRowTotalHtml(); ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($showLastRow && (($options = $block->getItemOptions()) || $block->escapeHtml($item->getDescription()))): ?> +<?php if ($showLastRow && (($options = $block->getItemOptions()) || $block->escapeHtml($item->getDescription()))) : ?> <tr> <td class="col options" colspan="5"> - <?php if ($options = $block->getItemOptions()): ?> + <?php if ($options = $block->getItemOptions()) : ?> <dl class="item-options"> <?php foreach ($options as $option) : ?> <dt><?= $block->escapeHtml($option['label']) ?></dt> - <?php if (!$block->getPrintStatus()): ?> + <?php if (!$block->getPrintStatus()) : ?> <?php $formattedOptionValue = $block->getFormatedOptionValue($option) ?> - <dd<?php if (isset($formattedOptionValue['full_view'])): ?> + <dd<?php if (isset($formattedOptionValue['full_view'])) : ?> class="tooltip wrapper" <?php endif; ?>> <?= /* @noEscape */ $formattedOptionValue['value'] ?> - <?php if (isset($formattedOptionValue['full_view'])): ?> + <?php if (isset($formattedOptionValue['full_view'])) : ?> <div class="tooltip content"> <dl class="item options"> <dt><?= $block->escapeHtml($option['label']); ?></dt> @@ -141,7 +137,7 @@ $prevOptionId = ''; </div> <?php endif; ?> </dd> - <?php else: ?> + <?php else : ?> <dd><?= $block->escapeHtml((isset($option['print_value']) ? $option['print_value'] : $option['value'])); ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml index 0cd39156b2513..c2ed3b161f629 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ ?> @@ -16,69 +14,69 @@ <?php $_prevOptionId = '' ?> -<?php foreach ($items as $_item): ?> +<?php foreach ($items as $_item) : ?> - <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?> + <?php if ($block->getItemOptions() || $parentItem->getDescription() || $this->helper(Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()) : ?> <?php $_showlastRow = true ?> - <?php else: ?> + <?php else : ?> <?php $_showlastRow = false ?> <?php endif; ?> - <?php if ($_item->getParentItem()): ?> + <?php if ($_item->getParentItem()) : ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($_prevOptionId != $attributes['option_id']) : ?> <tr class="options-label"> - <td colspan="3" class="col label"><div class="option label"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></div></td> + <td colspan="3" class="col label"><div class="option label"><?= $block->escapeHtml($attributes['option_label']) ?></div></td> </tr> <?php $_prevOptionId = $attributes['option_id'] ?> <?php endif; ?> <?php endif; ?> - <tr id="order-item-row-<?= /* @escapeNotVerified */ $_item->getId() ?>" class="<?php if ($_item->getParentItem()): ?>item-options-container<?php else: ?>item-parent<?php endif; ?>"<?php if ($_item->getParentItem()): ?> data-th="<?= /* @escapeNotVerified */ $attributes['option_label'] ?>"<?php endif; ?>> - <?php if (!$_item->getParentItem()): ?> - <td class="col name" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"> + <tr id="order-item-row-<?= $block->escapeHtmlAttr($_item->getId()) ?>" class="<?php if ($_item->getParentItem()) : ?>item-options-container<?php else : ?>item-parent<?php endif; ?>"<?php if ($_item->getParentItem()) : ?> data-th="<?= $block->escapeHtmlAttr($attributes['option_label']) ?>"<?php endif; ?>> + <?php if (!$_item->getParentItem()) : ?> + <td class="col name" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"> <strong class="product name product-item-name"><?= $block->escapeHtml($_item->getName()) ?></strong> </td> - <?php else: ?> - <td class="col value" data-th="<?= $block->escapeHtml(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> + <?php else : ?> + <td class="col value" data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>"><?= $block->getValueHtml($_item) ?></td> <?php endif; ?> - <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty Shipped')) ?>"> - <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())): ?> - <?php if (isset($shipItems[$_item->getId()])): ?> - <?= /* @escapeNotVerified */ $shipItems[$_item->getId()]->getQty()*1 ?> - <?php elseif ($_item->getIsVirtual()): ?> - <?= /* @escapeNotVerified */ __('N/A') ?> - <?php else: ?> + <td class="col sku" data-th="<?= $block->escapeHtmlAttr(__('SKU')) ?>"><?= $block->escapeHtml($_item->getSku()) ?></td> + <td class="col qty" data-th="<?= $block->escapeHtmlAttr(__('Qty Shipped')) ?>"> + <?php if (($block->isShipmentSeparately() && $_item->getParentItem()) || (!$block->isShipmentSeparately() && !$_item->getParentItem())) : ?> + <?php if (isset($shipItems[$_item->getId()])) : ?> + <?= (float)$shipItems[$_item->getId()]->getQty() * 1 ?> + <?php elseif ($_item->getIsVirtual()) : ?> + <?= $block->escapeHtml(__('N/A')) ?> + <?php else : ?> 0 <?php endif; ?> - <?php else: ?> + <?php else : ?>   <?php endif; ?> </td> </tr> <?php endforeach; ?> -<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))): ?> +<?php if ($_showlastRow && (($_options = $block->getItemOptions()) || $block->escapeHtml($_item->getDescription()))) : ?> <tr> <td class="col options" colspan="3"> - <?php if ($_options = $block->getItemOptions()): ?> + <?php if ($_options = $block->getItemOptions()) : ?> <dl class="item-options"> <?php foreach ($_options as $_option) : ?> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <?php if (!$block->getPrintStatus()): ?> + <?php if (!$block->getPrintStatus()) : ?> <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> - <dd<?php if (isset($_formatedOptionValue['full_view'])): ?> class="tooltip wrapper"<?php endif; ?>> - <?= /* @escapeNotVerified */ $_formatedOptionValue['value'] ?> - <?php if (isset($_formatedOptionValue['full_view'])): ?> + <dd<?php if (isset($_formatedOptionValue['full_view'])) : ?> class="tooltip wrapper"<?php endif; ?>> + <?= /* @noEscape */ $_formatedOptionValue['value'] ?> + <?php if (isset($_formatedOptionValue['full_view'])) : ?> <div class="tooltip content"> <dl class="item options"> <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <dd><?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?></dd> + <dd><?= /* @noEscape */ $_formatedOptionValue['full_view'] ?></dd> </dl> </div> <?php endif; ?> </dd> - <?php else: ?> + <?php else : ?> <dd><?= $block->escapeHtml((isset($_option['print_value']) ? $_option['print_value'] : $_option['value'])) ?></dd> <?php endif; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Bundle/view/frontend/web/js/float.js b/app/code/Magento/Bundle/view/frontend/web/js/float.js index 8e78fd069cb3b..dd8b1443dcbad 100644 --- a/app/code/Magento/Bundle/view/frontend/web/js/float.js +++ b/app/code/Magento/Bundle/view/frontend/web/js/float.js @@ -9,7 +9,7 @@ */ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js index 1e7fe6b6673d6..d5270569d5bbb 100644 --- a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js +++ b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js @@ -9,7 +9,7 @@ define([ 'jquery', 'mage/template', - 'jquery/ui', + 'jquery-ui-modules/widget', 'Magento_Bundle/js/price-bundle' ], function ($, mageTemplate) { 'use strict'; diff --git a/app/code/Magento/Bundle/view/frontend/web/js/slide.js b/app/code/Magento/Bundle/view/frontend/web/js/slide.js index 99b01f340fb4d..5afe4ad8a9ea7 100644 --- a/app/code/Magento/Bundle/view/frontend/web/js/slide.js +++ b/app/code/Magento/Bundle/view/frontend/web/js/slide.js @@ -8,7 +8,7 @@ */ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php index ee695c319501d..13bf10bc6aca7 100644 --- a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php +++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php @@ -117,7 +117,8 @@ private function fetch() : array 'price' => $link->getSelectionPriceValue(), 'position' => $link->getPosition(), 'id' => $link->getSelectionId(), - 'qty' => (int)$link->getSelectionQty(), + 'qty' => (float)$link->getSelectionQty(), + 'quantity' => (float)$link->getSelectionQty(), 'is_default' => (bool)$link->getIsDefault(), 'price_type' => $this->enumLookup->getEnumValueFromField( 'PriceTypeEnum', diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json index 7c5b27cfce5c3..2484436ea7edb 100644 --- a/app/code/Magento/BundleGraphQl/composer.json +++ b/app/code/Magento/BundleGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-catalog": "103.0.*", "magento/module-bundle": "100.3.*", "magento/module-catalog-graph-ql": "100.3.*", @@ -22,5 +22,5 @@ "Magento\\BundleGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/BundleGraphQl/etc/schema.graphqls b/app/code/Magento/BundleGraphQl/etc/schema.graphqls index edde6079dfb2f..74e21d3feaba2 100644 --- a/app/code/Magento/BundleGraphQl/etc/schema.graphqls +++ b/app/code/Magento/BundleGraphQl/etc/schema.graphqls @@ -1,35 +1,36 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. -type BundleItem @doc(description: "BundleItem defines an individual item in a bundle product") { - option_id: Int @doc(description: "An ID assigned to each type of item in a bundle product") - title: String @doc(description: "The display name of the item") - required: Boolean @doc(description: "Indicates whether the item must be included in the bundle") - type: String @doc(description: "The input type that the customer uses to select the item. Examples include radio button and checkbox") - position: Int @doc(description: "he relative position of this item compared to the other bundle items") - sku: String @doc(description: "The SKU of the bundle product") - options: [BundleItemOption] @doc(description: "An array of additional options for this bundle item") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\BundleItemLinks") +type BundleItem @doc(description: "BundleItem defines an individual item in a bundle product.") { + option_id: Int @doc(description: "An ID assigned to each type of item in a bundle product.") + title: String @doc(description: "The display name of the item.") + required: Boolean @doc(description: "Indicates whether the item must be included in the bundle.") + type: String @doc(description: "The input type that the customer uses to select the item. Examples include radio button and checkbox.") + position: Int @doc(description: "he relative position of this item compared to the other bundle items.") + sku: String @doc(description: "The SKU of the bundle product.") + options: [BundleItemOption] @doc(description: "An array of additional options for this bundle item.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\BundleItemLinks") } -type BundleItemOption @doc(description: "BundleItemOption defines characteristics and options for a specific bundle item") { - id: Int @doc(description: "The ID assigned to the bundled item option") - label: String @doc(description: "The text that identifies the bundled item option") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Options\\Label") - qty: Float @doc(description: "Indicates the quantity of this specific bundle item") - position: Int @doc(description: "When a bundle item contains multiple options, the relative position of this option compared to the other options") - is_default: Boolean @doc(description: "Indicates whether this option is the default option") - price: Float @doc(description: "The price of the selected option") - price_type: PriceTypeEnum @doc(description: "One of FIXED, PERCENT, or DYNAMIC") - can_change_quantity: Boolean @doc(description: "Indicates whether the customer can change the number of items for this option") - product: ProductInterface @doc(description: "Contains details about this product option") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product") +type BundleItemOption @doc(description: "BundleItemOption defines characteristics and options for a specific bundle item.") { + id: Int @doc(description: "The ID assigned to the bundled item option.") + label: String @doc(description: "The text that identifies the bundled item option.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Options\\Label") + qty: Float @deprecated(reason: "The `qty` is deprecated. Use `quantity` instead.") @doc(description: "Indicates the quantity of this specific bundle item.") + quantity: Float @doc(description: "Indicates the quantity of this specific bundle item.") + position: Int @doc(description: "When a bundle item contains multiple options, the relative position of this option compared to the other options.") + is_default: Boolean @doc(description: "Indicates whether this option is the default option.") + price: Float @doc(description: "The price of the selected option.") + price_type: PriceTypeEnum @doc(description: "One of FIXED, PERCENT, or DYNAMIC.") + can_change_quantity: Boolean @doc(description: "Indicates whether the customer can change the number of items for this option.") + product: ProductInterface @doc(description: "Contains details about this product option.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product") } -type BundleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "BundleProduct defines basic features of a bundle product and contains multiple BundleItems") { - price_view: PriceViewEnum @doc(description: "One of PRICE_RANGE or AS_LOW_AS") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\PriceView") - dynamic_price: Boolean @doc(description: "Indicates whether the bundle product has a dynamic price") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicPrice") - dynamic_sku: Boolean @doc(description: "Indicates whether the bundle product has a dynamic SK") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicSku") - ship_bundle_items: ShipBundleItemsEnum @doc(description: "Indicates whether to ship bundle items together or individually") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\ShipBundleItems") - dynamic_weight: Boolean @doc(description: "Indicates whether the bundle product has a dynamically calculated weight") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicWeight") - items: [BundleItem] @doc(description: "An array containing information about individual bundle items") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\BundleItems") +type BundleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "BundleProduct defines basic features of a bundle product and contains multiple BundleItems.") { + price_view: PriceViewEnum @doc(description: "One of PRICE_RANGE or AS_LOW_AS.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\PriceView") + dynamic_price: Boolean @doc(description: "Indicates whether the bundle product has a dynamic price.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicPrice") + dynamic_sku: Boolean @doc(description: "Indicates whether the bundle product has a dynamic SK.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicSku") + ship_bundle_items: ShipBundleItemsEnum @doc(description: "Indicates whether to ship bundle items together or individually.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\ShipBundleItems") + dynamic_weight: Boolean @doc(description: "Indicates whether the bundle product has a dynamically calculated weight.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Product\\Fields\\DynamicWeight") + items: [BundleItem] @doc(description: "An array containing information about individual bundle items.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\BundleItems") } enum PriceViewEnum @doc(description: "This enumeration defines whether a bundle product's price is displayed as the lowest possible value or as a range.") { diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index e3211aaa71c8f..6b889ab2dd4e4 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-bundle": "100.3.*", "magento/module-store": "101.0.*", @@ -27,5 +27,5 @@ "Magento\\BundleImportExport\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index 4772b15c79479..6e4d34e5db5c3 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-page-cache": "100.3.*" }, @@ -22,5 +22,5 @@ "Magento\\CacheInvalidate\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php index 8ed434e420f94..bbbbfb0a36e08 100644 --- a/app/code/Magento/Captcha/Model/DefaultModel.php +++ b/app/code/Magento/Captcha/Model/DefaultModel.php @@ -507,7 +507,7 @@ private function getWords() /** * Set captcha word * - * @param string $word + * @param string $word * @return $this * @since 100.2.0 */ diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml index 035e58de06ccf..977ee78c0d201 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -92,6 +92,8 @@ <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.sku$$)}}" stepKey="openProductPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart" /> + <waitForPageLoad stepKey="waitForAddToCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <waitForText userInput="You added $$createSimpleProduct.name$$ to your shopping cart." stepKey="waitForText"/> <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php index a1206a06dccd0..b569803078457 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php @@ -257,13 +257,15 @@ protected function _getSessionStub() ->getMock(); $session->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false)); - $session->setData([ - 'user_create_word' => [ - 'data' => 'AbCdEf5', - 'words' => 'AbCdEf5', - 'expires' => time() + self::EXPIRE_FRAME + $session->setData( + [ + 'user_create_word' => [ + 'data' => 'AbCdEf5', + 'words' => 'AbCdEf5', + 'expires' => time() + self::EXPIRE_FRAME + ] ] - ]); + ); return $session; } diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index 74cab4178b332..41154e27229e4 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-checkout": "100.3.*", @@ -28,5 +28,5 @@ "Magento\\Captcha\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml index 1be4bd19cd4ba..88e0d5edc2a7d 100644 --- a/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml +++ b/app/code/Magento/Captcha/view/adminhtml/templates/default.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */ /** @var \Magento\Captcha\Model\DefaultModel $captcha */ diff --git a/app/code/Magento/Captcha/view/frontend/templates/default.phtml b/app/code/Magento/Captcha/view/frontend/templates/default.phtml index 6c9a5fe85f596..ead8c590eee94 100644 --- a/app/code/Magento/Captcha/view/frontend/templates/default.phtml +++ b/app/code/Magento/Captcha/view/frontend/templates/default.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Captcha\Block\Captcha\DefaultCaptcha $block */ /** @var \Magento\Captcha\Model\DefaultModel $captcha */ diff --git a/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml b/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml index bad5acc209b5f..5902a9f25cc4b 100644 --- a/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml +++ b/app/code/Magento/Captcha/view/frontend/templates/js/components.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml() ?> diff --git a/app/code/Magento/Captcha/view/frontend/web/js/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/captcha.js index 0d4d4812f64c3..b5e5e6b006bfc 100644 --- a/app/code/Magento/Captcha/view/frontend/web/js/captcha.js +++ b/app/code/Magento/Captcha/view/frontend/web/js/captcha.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/CardinalCommerce/LICENSE.txt b/app/code/Magento/CardinalCommerce/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/CardinalCommerce/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 <insert your license name here>" 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/CardinalCommerce/LICENSE_AFL.txt b/app/code/Magento/CardinalCommerce/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/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 <insert your license name here>" 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/CardinalCommerce/Model/Adminhtml/Source/Environment.php b/app/code/Magento/CardinalCommerce/Model/Adminhtml/Source/Environment.php new file mode 100644 index 0000000000000..29e2939a0ae50 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Adminhtml/Source/Environment.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model\Adminhtml\Source; + +/** + * CardinalCommerce Environment Dropdown source + */ +class Environment implements \Magento\Framework\Data\OptionSourceInterface +{ + private const ENVIRONMENT_PRODUCTION = 'production'; + private const ENVIRONMENT_SANDBOX = 'sandbox'; + + /** + * Possible environment types + * + * @return array + */ + public function toOptionArray(): array + { + return [ + [ + 'value' => self::ENVIRONMENT_SANDBOX, + 'label' => 'Sandbox', + ], + [ + 'value' => self::ENVIRONMENT_PRODUCTION, + 'label' => 'Production' + ] + ]; + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Checkout/ConfigProvider.php b/app/code/Magento/CardinalCommerce/Model/Checkout/ConfigProvider.php new file mode 100644 index 0000000000000..a6794eae90084 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Checkout/ConfigProvider.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model\Checkout; + +use Magento\CardinalCommerce\Model\Config; +use Magento\CardinalCommerce\Model\Request\TokenBuilder; +use Magento\Checkout\Model\ConfigProviderInterface; + +/** + * Configuration provider. + */ +class ConfigProvider implements ConfigProviderInterface +{ + /** + * @var TokenBuilder + */ + private $requestJwtBuilder; + + /** + * @var Config + */ + private $config; + + /** + * @param TokenBuilder $requestJwtBuilder + * @param Config $config + */ + public function __construct( + TokenBuilder $requestJwtBuilder, + Config $config + ) { + $this->requestJwtBuilder = $requestJwtBuilder; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getConfig(): array + { + $config['cardinal'] = [ + 'environment' => $this->config->getEnvironment(), + 'requestJWT' => $this->requestJwtBuilder->build() + ]; + + return $config; + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Config.php b/app/code/Magento/CardinalCommerce/Model/Config.php new file mode 100644 index 0000000000000..64c72dae8d598 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Config.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * CardinalCommerce integration configuration. + * + * Class is a proxy service for retrieving configuration settings. + */ +class Config +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * Returns CardinalCommerce API Key used for authentication. + * + * A shared secret value between the merchant and Cardinal. This value should never be exposed to the public. + * + * @param int|null $storeId + * @return string + */ + public function getApiKey(?int $storeId = null): string + { + $apiKey = $this->scopeConfig->getValue( + 'three_d_secure/cardinal/api_key', + ScopeInterface::SCOPE_STORE, + $storeId + ); + return $apiKey; + } + + /** + * Returns CardinalCommerce API Identifier. + * + * GUID used to identify the specific API Key. + * + * @param int|null $storeId + * @return string + */ + public function getApiIdentifier(?int $storeId = null): string + { + $apiIdentifier = $this->scopeConfig->getValue( + 'three_d_secure/cardinal/api_identifier', + ScopeInterface::SCOPE_STORE, + $storeId + ); + return $apiIdentifier; + } + + /** + * Returns CardinalCommerce Org Unit Id. + * + * GUID to identify the merchant organization within Cardinal systems. + * + * @param int|null $storeId + * @return string + */ + public function getOrgUnitId(?int $storeId = null): string + { + $orgUnitId = $this->scopeConfig->getValue( + 'three_d_secure/cardinal/org_unit_id', + ScopeInterface::SCOPE_STORE, + $storeId + ); + return $orgUnitId; + } + + /** + * Returns CardinalCommerce environment. + * + * Sandbox or production. + * + * @param int|null $storeId + * @return string + */ + public function getEnvironment(?int $storeId = null): string + { + $environment = $this->scopeConfig->getValue( + 'three_d_secure/cardinal/environment', + ScopeInterface::SCOPE_STORE, + $storeId + ); + return $environment; + } + + /** + * If is "true" extra information about interaction with CardinalCommerce API are written to payment.log file + * + * @param int|null $storeId + * @return bool + */ + public function isDebugModeEnabled(?int $storeId = null): bool + { + $debugModeEnabled = $this->scopeConfig->isSetFlag( + 'three_d_secure/cardinal/debug', + ScopeInterface::SCOPE_STORE, + $storeId + ); + return $debugModeEnabled; + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/JwtManagement.php b/app/code/Magento/CardinalCommerce/Model/JwtManagement.php new file mode 100644 index 0000000000000..953af1751fd65 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/JwtManagement.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model; + +use Magento\Framework\Serialize\Serializer\Json; + +/** + * JSON Web Token management. + */ +class JwtManagement +{ + /** + * The signing algorithm. Cardinal supported algorithm is 'HS256' + */ + private const SIGN_ALGORITHM = 'HS256'; + + /** + * @var Json + */ + private $json; + + /** + * @param Json $json + */ + public function __construct( + Json $json + ) { + $this->json = $json; + } + + /** + * Converts JWT string into array. + * + * @param string $jwt The JWT + * @param string $key The secret key + * + * @return array + * @throws \InvalidArgumentException + */ + public function decode(string $jwt, string $key): array + { + if (empty($jwt)) { + throw new \InvalidArgumentException('JWT is empty'); + } + + $parts = explode('.', $jwt); + if (count($parts) != 3) { + throw new \InvalidArgumentException('Wrong number of segments in JWT'); + } + + [$headB64, $payloadB64, $signatureB64] = $parts; + + $headerJson = $this->urlSafeB64Decode($headB64); + $header = $this->json->unserialize($headerJson); + + $payloadJson = $this->urlSafeB64Decode($payloadB64); + $payload = $this->json->unserialize($payloadJson); + + $signature = $this->urlSafeB64Decode($signatureB64); + if ($signature !== $this->sign($headB64 . '.' . $payloadB64, $key, $header['alg'])) { + throw new \InvalidArgumentException('JWT signature verification failed'); + } + + return $payload; + } + + /** + * Converts and signs array into a JWT string. + * + * @param array $payload + * @param string $key + * + * @return string + * @throws \InvalidArgumentException + */ + public function encode(array $payload, string $key): string + { + $header = ['typ' => 'JWT', 'alg' => self::SIGN_ALGORITHM]; + + $headerJson = $this->json->serialize($header); + $segments[] = $this->urlSafeB64Encode($headerJson); + + $payloadJson = $this->json->serialize($payload); + $segments[] = $this->urlSafeB64Encode($payloadJson); + + $signature = $this->sign(implode('.', $segments), $key, $header['alg']); + $segments[] = $this->urlSafeB64Encode($signature); + + return implode('.', $segments); + } + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign. + * @param string $key The secret key. + * @param string $algorithm The signing algorithm. + * + * @return string + * @throws \InvalidArgumentException + */ + private function sign(string $msg, string $key, string $algorithm): string + { + if ($algorithm !== self::SIGN_ALGORITHM) { + throw new \InvalidArgumentException('Algorithm ' . $algorithm . ' is not supported'); + } + + return hash_hmac('sha256', $msg, $key, true); + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string + */ + private function urlSafeB64Decode(string $input): string + { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return base64_decode( + str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT) + ); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string + */ + private function urlSafeB64Encode(string $input): string + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Request/TokenBuilder.php b/app/code/Magento/CardinalCommerce/Model/Request/TokenBuilder.php new file mode 100644 index 0000000000000..e045d00dc55fe --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Request/TokenBuilder.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model\Request; + +use Magento\CardinalCommerce\Model\JwtManagement; +use Magento\CardinalCommerce\Model\Config; +use Magento\Checkout\Model\Session; +use Magento\Framework\DataObject\IdentityGeneratorInterface; +use Magento\Framework\Intl\DateTimeFactory; + +/** + * Cardinal request token builder. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class TokenBuilder +{ + /** + * @var JwtManagement + */ + private $jwtManagement; + + /** + * @var Session + */ + private $checkoutSession; + + /** + * @var Config + */ + private $config; + + /** + * @var IdentityGeneratorInterface + */ + private $identityGenerator; + + /** + * @var DateTimeFactory + */ + private $dateTimeFactory; + + /** + * @param JwtManagement $jwtManagement + * @param Session $checkoutSession + * @param Config $config + * @param IdentityGeneratorInterface $identityGenerator + * @param DateTimeFactory $dateTimeFactory + */ + public function __construct( + JwtManagement $jwtManagement, + Session $checkoutSession, + Config $config, + IdentityGeneratorInterface $identityGenerator, + DateTimeFactory $dateTimeFactory + ) { + $this->jwtManagement = $jwtManagement; + $this->checkoutSession = $checkoutSession; + $this->config = $config; + $this->identityGenerator = $identityGenerator; + $this->dateTimeFactory = $dateTimeFactory; + } + + /** + * Builds request JWT. + * + * @return string + */ + public function build() + { + $quote = $this->checkoutSession->getQuote(); + $currentDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC')); + $orderDetails = [ + 'OrderDetails' => [ + 'OrderNumber' => $quote->getId(), + 'Amount' => $quote->getBaseGrandTotal() * 100, + 'CurrencyCode' => $quote->getBaseCurrencyCode() + ] + ]; + + $token = [ + 'jti' => $this->identityGenerator->generateId(), + 'iss' => $this->config->getApiIdentifier(), + 'iat' => $currentDate->getTimestamp(), + 'OrgUnitId' => $this->config->getOrgUnitId(), + 'Payload' => $orderDetails, + 'ObjectifyPayload' => true + ]; + + $jwt = $this->jwtManagement->encode($token, $this->config->getApiKey()); + + return $jwt; + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php new file mode 100644 index 0000000000000..6b1a67a02a18d --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtParser.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model\Response; + +use Magento\CardinalCommerce\Model\JwtManagement; +use Magento\CardinalCommerce\Model\Config; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; +use Magento\Payment\Model\Method\Logger as PaymentLogger; + +/** + * Parses content of CardinalCommerce response JWT. + */ +class JwtParser implements JwtParserInterface +{ + /** + * @var JwtManagement + */ + private $jwtManagement; + + /** + * @var Config + */ + private $config; + + /** + * @var JwtPayloadValidatorInterface + */ + private $tokenValidator; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var PaymentLogger + */ + private $paymentLogger; + + /** + * @param JwtManagement $jwtManagement + * @param Config $config + * @param JwtPayloadValidatorInterface $tokenValidator + * @param PaymentLogger $paymentLogger + * @param LoggerInterface $logger + */ + public function __construct( + JwtManagement $jwtManagement, + Config $config, + JwtPayloadValidatorInterface $tokenValidator, + PaymentLogger $paymentLogger, + LoggerInterface $logger + ) { + $this->jwtManagement = $jwtManagement; + $this->config = $config; + $this->tokenValidator = $tokenValidator; + $this->paymentLogger = $paymentLogger; + $this->logger = $logger; + } + + /** + * Returns response JWT payload. + * + * @param string $jwt + * @return array + * @throws LocalizedException + */ + public function execute(string $jwt): array + { + $jwtPayload = ''; + try { + $this->debug(['Cardinal Response JWT:' => $jwt]); + $jwtPayload = $this->jwtManagement->decode($jwt, $this->config->getApiKey()); + $this->debug(['Cardinal Response JWT payload:' => $jwtPayload]); + if (!$this->tokenValidator->validate($jwtPayload)) { + $this->throwException(); + } + } catch (\InvalidArgumentException $e) { + $this->logger->critical($e, ['CardinalCommerce3DSecure']); + $this->throwException(); + } + + return $jwtPayload; + } + + /** + * Log JWT data. + * + * @param array $data + * @return void + */ + private function debug(array $data) + { + if ($this->config->isDebugModeEnabled()) { + $this->paymentLogger->debug($data, ['iss'], true); + } + } + + /** + * Throw general localized exception. + * + * @return void + * @throws LocalizedException + */ + private function throwException() + { + throw new LocalizedException( + __( + 'Authentication Failed. Your card issuer cannot authenticate this card. ' . + 'Please select another card or form of payment to complete your purchase.' + ) + ); + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php new file mode 100644 index 0000000000000..c6f9a5f60d10d --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtParserInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CardinalCommerce\Model\Response; + +/** + * Parses content of CardinalCommerce response JWT. + */ +interface JwtParserInterface +{ + /** + * Returns response JWT content. + * + * @param string $jwt + * @return array + */ + public function execute(string $jwt): array; +} diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidator.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidator.php new file mode 100644 index 0000000000000..9720b90cad915 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidator.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Model\Response; + +use Magento\Framework\Intl\DateTimeFactory; + +/** + * Validates payload of CardinalCommerce response JWT. + */ +class JwtPayloadValidator implements JwtPayloadValidatorInterface +{ + /** + * Resulting state of the transaction. + * + * SUCCESS - The transaction resulted in success for the payment type used. For example, + * with a CCA transaction this would indicate the user has successfully completed authentication. + * + * NOACTION - The transaction was successful but requires in no additional action. For example, + * with a CCA transaction this would indicate that the user is not currently enrolled in 3D Secure, + * but the API calls were successful. + * + * FAILURE - The transaction resulted in an error. For example, with a CCA transaction this would indicate + * that the user failed authentication or an error was encountered while processing the transaction. + * + * ERROR - A service level error was encountered. These are generally reserved for connectivity + * or API authentication issues. For example if your JWT was incorrectly signed, or Cardinal + * services are currently unreachable. + * + * @var array + */ + private $allowedActionCode = ['SUCCESS', 'NOACTION']; + + /** + * 3DS status of transaction from ECI Flag value. Liability shift applies. + * + * 05 - Successful 3D Authentication (Visa, AMEX, JCB) + * 02 - Successful 3D Authentication (MasterCard) + * 06 - Attempted Processing or User Not Enrolled (Visa, AMEX, JCB) + * 01 - Attempted Processing or User Not Enrolled (MasterCard) + * 07 - 3DS authentication is either failed or could not be attempted; + * possible reasons being both card and Issuing Bank are not secured by 3DS, + * technical errors, or improper configuration. (Visa, AMEX, JCB) + * 00 - 3DS authentication is either failed or could not be attempted; + * possible reasons being both card and Issuing Bank are not secured by 3DS, + * technical errors, or improper configuration. (MasterCard) + * + * @var array + */ + private $allowedECIFlag = ['05', '02', '06', '01']; + + /** + * @var DateTimeFactory + */ + private $dateTimeFactory; + + /** + * @param DateTimeFactory $dateTimeFactory + */ + public function __construct( + DateTimeFactory $dateTimeFactory + ) { + $this->dateTimeFactory = $dateTimeFactory; + } + /** + * @inheritdoc + */ + public function validate(array $jwtPayload): bool + { + $transactionState = $jwtPayload['Payload']['ActionCode'] ?? ''; + $errorNumber = $jwtPayload['Payload']['ErrorNumber'] ?? -1; + $eciFlag = $jwtPayload['Payload']['Payment']['ExtendedData']['ECIFlag'] ?? ''; + $expTimestamp = $jwtPayload['exp'] ?? 0; + + return $this->isValidErrorNumber((int)$errorNumber) + && $this->isValidTransactionState($transactionState) + && $this->isValidEciFlag($eciFlag) + && $this->isNotExpired((int)$expTimestamp); + } + + /** + * Checks application error number. + * + * A non-zero value represents the error encountered while attempting the process the message request. + * + * @param int $errorNumber + * @return bool + */ + private function isValidErrorNumber(int $errorNumber) + { + return $errorNumber === 0; + } + + /** + * Checks if value of transaction state identifier is in allowed list. + * + * @param string $transactionState + * @return bool + */ + private function isValidTransactionState(string $transactionState) + { + return in_array($transactionState, $this->allowedActionCode); + } + + /** + * Checks if value of ECI Flag identifier is in allowed list. + * + * @param string $eciFlag + * @return bool + */ + private function isValidEciFlag(string $eciFlag) + { + return in_array($eciFlag, $this->allowedECIFlag); + } + + /** + * Checks if token is not expired. + * + * @param int $expTimestamp + * @return bool + */ + private function isNotExpired(int $expTimestamp) + { + $currentDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC')); + + return $currentDate->getTimestamp() < $expTimestamp; + } +} diff --git a/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidatorInterface.php b/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidatorInterface.php new file mode 100644 index 0000000000000..774c0daee6ca2 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Model/Response/JwtPayloadValidatorInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CardinalCommerce\Model\Response; + +/** + * Validates payload of CardinalCommerce response JWT. + */ +interface JwtPayloadValidatorInterface +{ + /** + * Validates token payload. + * + * @param array $jwtPayload + * @return bool + */ + public function validate(array $jwtPayload); +} diff --git a/app/code/Magento/CardinalCommerce/README.md b/app/code/Magento/CardinalCommerce/README.md new file mode 100644 index 0000000000000..54db9114a2a0e --- /dev/null +++ b/app/code/Magento/CardinalCommerce/README.md @@ -0,0 +1 @@ +The CardinalCommerce module provides a possibility to enable 3-D Secure 2.0 support for payment methods. \ No newline at end of file diff --git a/app/code/Magento/CardinalCommerce/Test/Unit/Model/JwtManagementTest.php b/app/code/Magento/CardinalCommerce/Test/Unit/Model/JwtManagementTest.php new file mode 100644 index 0000000000000..70eae201c157a --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Test/Unit/Model/JwtManagementTest.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Test\Unit\Model; + +use Magento\CardinalCommerce\Model\JwtManagement; +use Magento\Framework\Serialize\Serializer\Json; + +/** + * Tests JWT encode and decode. + */ +class JwtManagementTest extends \PHPUnit\Framework\TestCase +{ + /** + * API key + */ + private const API_KEY = 'API key'; + + /** + * @var JwtManagement + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->model = new JwtManagement(new Json()); + } + + /** + * Tests JWT encode. + */ + public function testEncode() + { + $jwt = $this->model->encode($this->getTokenPayload(), self::API_KEY); + + $this->assertEquals( + $this->getValidHS256Jwt(), + $jwt, + 'Generated JWT isn\'t equal to expected' + ); + } + + /** + * Tests JWT decode. + */ + public function testDecode() + { + $tokenPayload = $this->model->decode($this->getValidHS256Jwt(), self::API_KEY); + + $this->assertEquals( + $this->getTokenPayload(), + $tokenPayload, + 'JWT payload isn\'t equal to expected' + ); + } + + /** + * Tests JWT decode. + * + * @param string $jwt + * @param string $errorMessage + * @dataProvider decodeWithExceptionDataProvider + */ + public function testDecodeWithException(string $jwt, string $errorMessage) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($errorMessage); + + $this->model->decode($jwt, self::API_KEY); + } + + /** + * @return array + */ + public function decodeWithExceptionDataProvider(): array + { + return [ + [ + 'jwt' => '', + 'errorMessage' => 'JWT is empty', + ], + [ + 'jwt' => 'dddd.dddd', + 'errorMessage' => 'Wrong number of segments in JWT', + ], + [ + 'jwt' => 'dddd.dddd.dddd', + 'errorMessage' => 'Unable to unserialize value. Error: Syntax error', + ], + [ + 'jwt' => $this->getHS512Jwt(), + 'errorMessage' => 'Algorithm HS512 is not supported', + ], + [ + 'jwt' => $this->getJwtWithInvalidSignature(), + 'errorMessage' => 'JWT signature verification failed', + ], + ]; + } + + /** + * Returns valid JWT, signed using HS256. + * + * @return string + */ + private function getValidHS256Jwt(): string + { + return 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNWE1OWJmYi1hYzA2LTRjNWYtYmU1Yy0zNTFiNjR' . + 'hZTYwOGUiLCJpc3MiOiI1NjU2MGEzNThiOTQ2ZTBjODQ1MjM2NWRzIiwiaWF0IjoiMTQ0ODk5Nzg2NSIsIk9yZ1Vua' . + 'XRJZCI6IjU2NTYwN2MxOGI5NDZlMDU4NDYzZHM4ciIsIlBheWxvYWQiOnsiT3JkZXJEZXRhaWxzIjp7Ik9yZGVyTnV' . + 'tYmVyIjoiMTI1IiwiQW1vdW50IjoiMTUwMCIsIkN1cnJlbmN5Q29kZSI6IlVTRCJ9fSwiT2JqZWN0aWZ5UGF5bG9hZ' . + 'CI6dHJ1ZX0.emv9N75JIvyk_gQHMNJYQ2UzmbM5ISBQs1Y222zO1jk'; + } + + /** + * Returns JWT, signed using not supported HS512. + * + * @return string + */ + private function getHS512Jwt(): string + { + return 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJhNWE1OWJmYi1hYzA2LTRjNWYtYmU1Yy0zNTFiNjR' . + 'hZTYwOGUiLCJpc3MiOiI1NjU2MGEzNThiOTQ2ZTBjODQ1MjM2NWRzIiwiaWF0IjoiMTQ0ODk5Nzg2NSIsIk9yZ1V' . + 'uaXRJZCI6IjU2NTYwN2MxOGI5NDZlMDU4NDYzZHM4ciIsIlBheWxvYWQiOnsiT3JkZXJEZXRhaWxzIjp7Ik9yZGV' . + 'yTnVtYmVyIjoiMTI1IiwiQW1vdW50IjoiMTUwMCIsIkN1cnJlbmN5Q29kZSI6IlVTRCJ9fSwiT2JqZWN0aWZ5UGF' . + '5bG9hZCI6dHJ1ZX0.4fwdXfIgUe7bAiHP2bZsxSG-s-wJOyaCmCe9MOQhs-m6LLjRGarguA_0SqZA2EeUaytMO4R' . + 'G84ZEOfbYfS8c1A'; + } + + /** + * Returns JWT with invalid signature. + * + * @return string + */ + private function getJwtWithInvalidSignature(): string + { + return 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNWE1OWJmYi1hYzA2LTRjNWYtYmU1Yy0zNTFiNjR' . + 'hZTYwOGUiLCJpc3MiOiI1NjU2MGEzNThiOTQ2ZTBjODQ1MjM2NWRzIiwiaWF0IjoiMTQ0ODk5Nzg2NSIsIk9yZ1Vua' . + 'XRJZCI6IjU2NTYwN2MxOGI5NDZlMDU4NDYzZHM4ciIsIlBheWxvYWQiOnsiT3JkZXJEZXRhaWxzIjp7Ik9yZGVyTnV' . + 'tYmVyIjoiMTI1IiwiQW1vdW50IjoiMTUwMCIsIkN1cnJlbmN5Q29kZSI6IlVTRCJ9fSwiT2JqZWN0aWZ5UGF5bG9hZ' . + 'CI6dHJ1ZX0.InvalidSign'; + } + + /** + * Returns token decoded payload. + * + * @return array + */ + private function getTokenPayload(): array + { + return [ + 'jti' => 'a5a59bfb-ac06-4c5f-be5c-351b64ae608e', + 'iss' => '56560a358b946e0c8452365ds', + 'iat' => '1448997865', + 'OrgUnitId' => '565607c18b946e058463ds8r', + 'Payload' => [ + 'OrderDetails' => [ + 'OrderNumber' => '125', + 'Amount' => '1500', + 'CurrencyCode' => 'USD' + ] + ], + 'ObjectifyPayload' => true + ]; + } +} diff --git a/app/code/Magento/CardinalCommerce/Test/Unit/Model/Response/JwtPayloadValidatorTest.php b/app/code/Magento/CardinalCommerce/Test/Unit/Model/Response/JwtPayloadValidatorTest.php new file mode 100644 index 0000000000000..cbaae9f777a61 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Test/Unit/Model/Response/JwtPayloadValidatorTest.php @@ -0,0 +1,202 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CardinalCommerce\Test\Unit\Model\Response; + +use Magento\CardinalCommerce\Model\Response\JwtPayloadValidator; +use Magento\Framework\Intl\DateTimeFactory; + +/** + * Class JwtPayloadValidatorTest + */ +class JwtPayloadValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var JwtPayloadValidator + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->model = new JwtPayloadValidator(new DateTimeFactory()); + } + + /** + * Tests successful cases. + * + * @param array $token + * @dataProvider validateSuccessDataProvider + */ + public function testValidateSuccess(array $token) + { + $this->assertTrue( + $this->model->validate($token) + ); + } + + /** + * @case 1. All data are correct, the transaction was successful (Visa, AMEX) + * @case 2. All data are correct, the transaction was successful but requires in no additional action (Visa, AMEX) + * @case 3. All data are correct, the transaction was successful (MasterCard) + * @case 4. All data are correct, the transaction was successful but requires in no additional action (MasterCard) + * + * @return array + */ + public function validateSuccessDataProvider() + { + $expTimestamp = $this->getValidExpTimestamp(); + + return [ + 1 => $this->createToken('05', '0', 'SUCCESS', $expTimestamp), + 2 => $this->createToken('06', '0', 'NOACTION', $expTimestamp), + 3 => $this->createToken('02', '0', 'SUCCESS', $expTimestamp), + 4 => $this->createToken('01', '0', 'NOACTION', $expTimestamp), + ]; + } + + /** + * Case when 3DS authentication is either failed or could not be attempted. + * + * @param array $token + * @dataProvider validationEciFailsDataProvider + */ + public function testValidationEciFails(array $token) + { + $this->assertFalse( + $this->model->validate($token), + 'Negative ECIFlag value validation fails' + ); + } + + /** + * ECIFlag value when 3DS authentication is either failed or could not be attempted. + * + * @case 1. Visa, AMEX, JCB + * @case 2. MasterCard + * @return array + */ + public function validationEciFailsDataProvider(): array + { + $expTimestamp = $this->getValidExpTimestamp(); + return [ + 1 => $this->createToken('07', '0', 'NOACTION', $expTimestamp), + 2 => $this->createToken('00', '0', 'NOACTION', $expTimestamp), + ]; + } + + /** + * Case when resulting state of the transaction is negative. + * + * @param array $token + * @dataProvider validationActionCodeFailsDataProvider + */ + public function testValidationActionCodeFails(array $token) + { + $this->assertFalse( + $this->model->validate($token), + 'Negative ActionCode value validation fails' + ); + } + + /** + * ECIFlag value when 3DS authentication is either failed or could not be attempted. + * + * @return array + */ + public function validationActionCodeFailsDataProvider(): array + { + $expTimestamp = $this->getValidExpTimestamp(); + return [ + $this->createToken('05', '0', 'FAILURE', $expTimestamp), + $this->createToken('05', '0', 'ERROR', $expTimestamp), + ]; + } + + /** + * Case when ErrorNumber not equal 0. + */ + public function testValidationErrorNumberFails() + { + $notAllowedErrorNumber = '10'; + $expTimestamp = $this->getValidExpTimestamp(); + $token = $this->createToken('05', $notAllowedErrorNumber, 'SUCCESS', $expTimestamp); + $this->assertFalse( + $this->model->validate($token), + 'Negative ErrorNumber value validation fails' + ); + } + + /** + * Case when ErrorNumber not equal 0. + */ + public function testValidationExpirationFails() + { + $expTimestamp = $this->getOutdatedExpTimestamp(); + $token = $this->createToken('05', '0', 'SUCCESS', $expTimestamp); + $this->assertFalse( + $this->model->validate($token), + 'Expiration date validation fails' + ); + } + + /** + * Creates a token. + * + * @param string $eciFlag + * @param string $errorNumber + * @param string $actionCode + * @param int $expTimestamp + * + * @return array + */ + private function createToken(string $eciFlag, string $errorNumber, string $actionCode, int $expTimestamp): array + { + return [ + [ + 'Payload' => [ + 'Payment' => [ + 'ExtendedData' => [ + 'ECIFlag' => $eciFlag, + ], + ], + 'ActionCode' => $actionCode, + 'ErrorNumber' => $errorNumber + ], + 'exp' => $expTimestamp + ] + ]; + } + + /** + * Returns valid expiration timestamp. + * + * @return int + */ + private function getValidExpTimestamp() + { + $dateTimeFactory = new DateTimeFactory(); + $currentDate = $dateTimeFactory->create('now', new \DateTimeZone('UTC')); + + return $currentDate->getTimestamp() + 3600; + } + + /** + * Returns outdated expiration timestamp. + * + * @return int + */ + private function getOutdatedExpTimestamp() + { + $dateTimeFactory = new DateTimeFactory(); + $currentDate = $dateTimeFactory->create('now', new \DateTimeZone('UTC')); + + return $currentDate->getTimestamp() - 3600; + } +} diff --git a/app/code/Magento/CardinalCommerce/composer.json b/app/code/Magento/CardinalCommerce/composer.json new file mode 100644 index 0000000000000..d7f5285fc6675 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/composer.json @@ -0,0 +1,28 @@ +{ + "name": "magento/module-cardinal-commerce", + "description": "Provides a possibility to enable 3-D Secure 2.0 support for payment methods.", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "102.0.*", + "magento/module-checkout": "100.3.*", + "magento/module-payment": "100.3.*", + "magento/module-store": "101.0.*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CardinalCommerce\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml b/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..532fcdd0f598f --- /dev/null +++ b/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="three_d_secure" translate="label" type="text" sortOrder="410" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>3D Secure</label> + <tab>sales</tab> + <resource>Magento_Sales::three_d_secure</resource> + <group id="cardinal" type="text" sortOrder="13" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="config" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>CardinalCommerce</label> + <comment><![CDATA[Please visit <a href="https://www.cardinalcommerce.com/" target="_blank">www.cardinalcommerce.com</a> to get the CardinalCommerce credentials and find out more details about PSD2 SCA requirements. For support contact <a href="mailto:support@cardinalcommerce.com">support@cardinalcommerce.com</a>.]]></comment> + <field id="environment" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Environment</label> + <source_model>Magento\CardinalCommerce\Model\Adminhtml\Source\Environment</source_model> + <config_path>three_d_secure/cardinal/environment</config_path> + </field> + <field id="org_unit_id" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Org Unit Id</label> + <config_path>three_d_secure/cardinal/org_unit_id</config_path> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + </field> + <field id="api_key" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>API Key</label> + <config_path>three_d_secure/cardinal/api_key</config_path> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + </field> + <field id="api_identifier" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>API Identifier</label> + <config_path>three_d_secure/cardinal/api_identifier</config_path> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + </field> + <field id="debug" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Debug</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <config_path>three_d_secure/cardinal/debug</config_path> + </field> + </group> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/CardinalCommerce/etc/config.xml b/app/code/Magento/CardinalCommerce/etc/config.xml new file mode 100644 index 0000000000000..605ba89c12850 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/etc/config.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <dev> + <js> + <minify_exclude> + <cardinal_commerce>/v1/songbird</cardinal_commerce> + </minify_exclude> + </js> + </dev> + <three_d_secure> + <cardinal> + <environment>production</environment> + <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> + <org_unit_id backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> + <api_identifier backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> + <debug>0</debug> + </cardinal> + </three_d_secure> + </default> +</config> diff --git a/app/code/Magento/CardinalCommerce/etc/di.xml b/app/code/Magento/CardinalCommerce/etc/di.xml new file mode 100644 index 0000000000000..410c16e91cb77 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/etc/di.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\CardinalCommerce\Model\Response\JwtPayloadValidatorInterface" type="Magento\CardinalCommerce\Model\Response\JwtPayloadValidator" /> + <preference for="Magento\CardinalCommerce\Model\Response\JwtParserInterface" type="Magento\CardinalCommerce\Model\Response\JwtParser" /> +</config> diff --git a/app/code/Magento/CardinalCommerce/etc/frontend/di.xml b/app/code/Magento/CardinalCommerce/etc/frontend/di.xml new file mode 100644 index 0000000000000..e3913291aa683 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/etc/frontend/di.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Checkout\Model\CompositeConfigProvider"> + <arguments> + <argument name="configProviders" xsi:type="array"> + <item name="cardinal_config_provider" xsi:type="object"> + Magento\CardinalCommerce\Model\Checkout\ConfigProvider + </item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/CardinalCommerce/etc/module.xml b/app/code/Magento/CardinalCommerce/etc/module.xml new file mode 100644 index 0000000000000..8605e81d3fda7 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_CardinalCommerce" > + <sequence> + <module name="Magento_Checkout"/> + <module name="Magento_Payment"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/CardinalCommerce/registration.php b/app/code/Magento/CardinalCommerce/registration.php new file mode 100644 index 0000000000000..26fb168fb0ae2 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_CardinalCommerce', __DIR__); diff --git a/app/code/Magento/CardinalCommerce/view/frontend/requirejs-config.js b/app/code/Magento/CardinalCommerce/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..0c5e3964d04e7 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/view/frontend/requirejs-config.js @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + shim: { + cardinaljs: { + exports: 'Cardinal' + }, + cardinaljsSandbox: { + exports: 'Cardinal' + } + }, + paths: { + cardinaljsSandbox: 'https://includestest.ccdc02.com/cardinalcruise/v1/songbird', + cardinaljs: 'https://songbird.cardinalcommerce.com/edge/v1/songbird' + } +}; + diff --git a/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-client.js b/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-client.js new file mode 100644 index 0000000000000..2ddb450d2f81c --- /dev/null +++ b/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-client.js @@ -0,0 +1,131 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'uiClass', + 'Magento_CardinalCommerce/js/cardinal-factory', + 'Magento_Checkout/js/model/quote', + 'mage/translate' +], function ($, Class, cardinalFactory, quote, $t) { + 'use strict'; + + return { + /** + * Starts Cardinal Consumer Authentication + * + * @param {Object} cardData + * @return {jQuery.Deferred} + */ + startAuthentication: function (cardData) { + var deferred = $.Deferred(); + + if (this.cardinalClient) { + this._startAuthentication(deferred, cardData); + } else { + cardinalFactory(this.getEnvironment()) + .done(function (client) { + this.cardinalClient = client; + this._startAuthentication(deferred, cardData); + }.bind(this)); + } + + return deferred.promise(); + }, + + /** + * Cardinal Consumer Authentication + * + * @param {jQuery.Deferred} deferred + * @param {Object} cardData + */ + _startAuthentication: function (deferred, cardData) { + //this.cardinalClient.configure({ logging: { level: 'verbose' } }); + this.cardinalClient.on('payments.validated', function (data, jwt) { + if (data.ErrorNumber !== 0) { + deferred.reject(data.ErrorDescription); + } else if ($.inArray(data.ActionCode, ['FAILURE', 'ERROR']) !== -1) { + deferred.reject($t('Authentication Failed. Please try again with another form of payment.')); + } else { + deferred.resolve(jwt); + } + this.cardinalClient.off('payments.validated'); + }.bind(this)); + + this.cardinalClient.on('payments.setupComplete', function () { + this.cardinalClient.start('cca', this.getRequestOrderObject(cardData)); + this.cardinalClient.off('payments.setupComplete'); + }.bind(this)); + + this.cardinalClient.setup('init', { + jwt: this.getRequestJWT() + }); + }, + + /** + * Returns request order object. + * + * The request order object is structured object that is used to pass data + * to Cardinal that describes an order you'd like to process. + * + * If you pass a request object in both the JWT and the browser, + * Cardinal will merge the objects together where the browser overwrites + * the JWT object as it is considered the most recently captured data. + * + * @param {Object} cardData + * @returns {Object} + */ + getRequestOrderObject: function (cardData) { + var totalAmount = quote.totals()['base_grand_total'], + currencyCode = quote.totals()['base_currency_code'], + billingAddress = quote.billingAddress(), + requestObject; + + requestObject = { + OrderDetails: { + Amount: totalAmount * 100, + CurrencyCode: currencyCode + }, + Consumer: { + Account: { + AccountNumber: cardData.accountNumber, + ExpirationMonth: cardData.expMonth, + ExpirationYear: cardData.expYear, + CardCode: cardData.cardCode + }, + BillingAddress: { + FirstName: billingAddress.firstname, + LastName: billingAddress.lastname, + Address1: billingAddress.street[0], + Address2: billingAddress.street[1], + City: billingAddress.city, + State: billingAddress.region, + PostalCode: billingAddress.postcode, + CountryCode: billingAddress.countryId, + Phone1: billingAddress.telephone + } + } + }; + + return requestObject; + }, + + /** + * Returns request JWT + * @returns {String} + */ + getRequestJWT: function () { + return window.checkoutConfig.cardinal.requestJWT; + }, + + /** + * Returns type of environment + * @returns {String} + */ + getEnvironment: function () { + return window.checkoutConfig.cardinal.environment; + } + }; +}); diff --git a/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-factory.js b/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-factory.js new file mode 100644 index 0000000000000..1da92ba2ff787 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/view/frontend/web/js/cardinal-factory.js @@ -0,0 +1,29 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return function (environment) { + var deferred = $.Deferred(), + dependency = 'cardinaljs'; + + if (environment === 'sandbox') { + dependency = 'cardinaljsSandbox'; + } + + require( + [dependency], + function (Cardinal) { + deferred.resolve(Cardinal); + }, + deferred.reject + ); + + return deferred.promise(); + }; +}); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php index 83ec501592489..9a4a9fa768006 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php @@ -253,7 +253,7 @@ public function getLoadTreeUrl($expanded = null) */ public function getNodesUrl() { - return $this->getUrl('catalog/category/jsonTree'); + return $this->getUrl('catalog/category/tree'); } /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg.php b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg.php index 62c27a421419d..c58ed58370e3a 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg.php @@ -11,6 +11,9 @@ */ namespace Magento\Catalog\Block\Adminhtml\Helper\Form; +/** + * Wysiwyg helper. + */ class Wysiwyg extends \Magento\Framework\Data\Form\Element\Textarea { /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php index 26846b9d42ed1..e90b0fbc88e94 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php @@ -12,8 +12,18 @@ */ namespace Magento\Catalog\Block\Adminhtml\Product; +use Magento\Framework\Escaper; + +/** + * Class Edit + */ class Edit extends \Magento\Backend\Block\Widget { + /** + * @var Escaper + */ + private $escaper; + /** * @var string */ @@ -47,6 +57,7 @@ class Edit extends \Magento\Backend\Block\Widget * @param \Magento\Eav\Model\Entity\Attribute\SetFactory $attributeSetFactory * @param \Magento\Framework\Registry $registry * @param \Magento\Catalog\Helper\Product $productHelper + * @param Escaper $escaper * @param array $data */ public function __construct( @@ -55,16 +66,20 @@ public function __construct( \Magento\Eav\Model\Entity\Attribute\SetFactory $attributeSetFactory, \Magento\Framework\Registry $registry, \Magento\Catalog\Helper\Product $productHelper, + Escaper $escaper, array $data = [] ) { $this->_productHelper = $productHelper; $this->_attributeSetFactory = $attributeSetFactory; $this->_coreRegistry = $registry; $this->jsonEncoder = $jsonEncoder; + $this->escaper = $escaper; parent::__construct($context, $data); } /** + * Edit Product constructor + * * @return void */ protected function _construct() @@ -144,6 +159,8 @@ protected function _prepareLayout() } /** + * Retrieve back button html + * * @return string */ public function getBackButtonHtml() @@ -152,6 +169,8 @@ public function getBackButtonHtml() } /** + * Retrieve cancel button html + * * @return string */ public function getCancelButtonHtml() @@ -160,6 +179,8 @@ public function getCancelButtonHtml() } /** + * Retrieve save button html + * * @return string */ public function getSaveButtonHtml() @@ -168,6 +189,8 @@ public function getSaveButtonHtml() } /** + * Retrieve save and edit button html + * * @return string */ public function getSaveAndEditButtonHtml() @@ -176,6 +199,8 @@ public function getSaveAndEditButtonHtml() } /** + * Retrieve delete button html + * * @return string */ public function getDeleteButtonHtml() @@ -194,6 +219,8 @@ public function getSaveSplitButtonHtml() } /** + * Retrieve validation url + * * @return string */ public function getValidationUrl() @@ -202,6 +229,8 @@ public function getValidationUrl() } /** + * Retrieve save url + * * @return string */ public function getSaveUrl() @@ -210,6 +239,8 @@ public function getSaveUrl() } /** + * Retrieve save and continue url + * * @return string */ public function getSaveAndContinueUrl() @@ -221,6 +252,8 @@ public function getSaveAndContinueUrl() } /** + * Retrieve product id + * * @return mixed */ public function getProductId() @@ -229,6 +262,8 @@ public function getProductId() } /** + * Retrieve product set id + * * @return mixed */ public function getProductSetId() @@ -241,6 +276,8 @@ public function getProductSetId() } /** + * Retrieve duplicate url + * * @return string */ public function getDuplicateUrl() @@ -249,6 +286,8 @@ public function getDuplicateUrl() } /** + * Retrieve product header + * * @deprecated 102.0.0 * @return string */ @@ -263,6 +302,8 @@ public function getHeader() } /** + * Get product attribute set name + * * @return string */ public function getAttributeSetName() @@ -275,11 +316,14 @@ public function getAttributeSetName() } /** + * Retrieve id of selected tab + * * @return string */ public function getSelectedTabId() { - return addslashes(htmlspecialchars($this->getRequest()->getParam('tab'))); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return addslashes($this->escaper->escapeHtml($this->getRequest()->getParam('tab'))); } /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/AttributeSet.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/AttributeSet.php index 3467c7aac289b..d95ee7f8f2cf9 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/AttributeSet.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/AttributeSet.php @@ -11,6 +11,9 @@ */ namespace Magento\Catalog\Block\Adminhtml\Product\Edit; +/** + * Admin AttributeSet block + */ class AttributeSet extends \Magento\Backend\Block\Widget\Form { /** @@ -42,12 +45,14 @@ public function __construct( public function getSelectorOptions() { return [ - 'source' => $this->getUrl('catalog/product/suggestAttributeSets'), + 'source' => $this->escapeUrl($this->getUrl('catalog/product/suggestAttributeSets')), 'className' => 'category-select', 'showRecent' => true, 'storageKey' => 'product-template-key', 'minLength' => 0, - 'currentlySelected' => $this->_coreRegistry->registry('product')->getAttributeSetId() + 'currentlySelected' => $this->escapeHtml( + $this->_coreRegistry->registry('product')->getAttributeSetId() + ) ]; } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Price.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Price.php index 3b4a5f9eabfd2..386fe1333a7e9 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Price.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Price.php @@ -48,6 +48,8 @@ public function __construct( } /** + * Construct. + * * @return void */ protected function _construct() @@ -63,6 +65,8 @@ protected function _construct() } /** + * @inheritDoc + * * @return Grid */ protected function _prepareCollection() @@ -80,6 +84,8 @@ protected function _prepareCollection() } /** + * @inheritDoc + * * @return $this */ protected function _prepareColumns() @@ -116,6 +122,8 @@ protected function _prepareColumns() } /** + * @inheritDoc + * * @return string */ public function getGridUrl() diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Stock.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Stock.php index d572690143ca4..ede478cabe783 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Stock.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Alerts/Stock.php @@ -48,6 +48,8 @@ public function __construct( } /** + * Construct. + * * @return void */ protected function _construct() @@ -63,6 +65,8 @@ protected function _construct() } /** + * @inheritDoc + * * @return Grid */ protected function _prepareCollection() @@ -80,6 +84,8 @@ protected function _prepareCollection() } /** + * @inheritDoc + * * @return $this */ protected function _prepareColumns() @@ -103,6 +109,8 @@ protected function _prepareColumns() } /** + * Get grid url. + * * @return string */ public function getGridUrl() diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes/Search.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes/Search.php index e1b97f996c769..42463354926dd 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes/Search.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes/Search.php @@ -11,6 +11,9 @@ */ namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Attributes; +/** + * Admin product attribute search block + */ class Search extends \Magento\Backend\Block\Widget { /** @@ -62,13 +65,15 @@ protected function _construct() } /** + * Get selector options + * * @return array */ public function getSelectorOptions() { $templateId = $this->_coreRegistry->registry('product')->getAttributeSetId(); return [ - 'source' => $this->getUrl('catalog/product/suggestAttributes'), + 'source' => $this->escapeUrl($this->getUrl('catalog/product/suggestAttributes')), 'minLength' => 0, 'ajaxOptions' => ['data' => ['template_id' => $templateId]], 'template' => '[data-template-for="product-attribute-search-' . $this->getGroupId() . '"]', @@ -110,6 +115,8 @@ public function getSuggestedAttributes($labelPart, $templateId = null) } /** + * Get add attribute url + * * @return string */ public function getAddAttributeUrl() diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php index 20e12889cae0d..782147e1e8ef6 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php @@ -79,6 +79,8 @@ public function __construct( } /** + * Get backorder option. + * * @return array */ public function getBackordersOption() @@ -128,6 +130,8 @@ public function getStockItem() } /** + * Get field value. + * * @param string $field * @return string|null */ @@ -145,6 +149,8 @@ public function getFieldValue($field) } /** + * Get config field value. + * * @param string $field * @return string|null */ @@ -163,6 +169,8 @@ public function getConfigFieldValue($field) } /** + * Get default config value. + * * @param string $field * @return string|null */ @@ -182,6 +190,8 @@ public function isReadonly() } /** + * Is new. + * * @return bool */ public function isNew() @@ -193,6 +203,8 @@ public function isNew() } /** + * Get field suffix. + * * @return string */ public function getFieldSuffix() @@ -221,6 +233,8 @@ public function isVirtual() } /** + * Is single store mode enabled. + * * @return bool */ public function isSingleStoreMode() diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Grid.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Grid.php index 8f3a0793cc49f..01408ade56432 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Grid.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Grid.php @@ -8,6 +8,8 @@ use Magento\Store\Model\Store; /** + * Catalog product grid + * * @api * @since 100.0.2 */ @@ -85,7 +87,7 @@ public function __construct( } /** - * @return void + * @inheritDoc */ protected function _construct() { @@ -99,7 +101,10 @@ protected function _construct() } /** + * Get store. + * * @return Store + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _getStore() { @@ -108,7 +113,7 @@ protected function _getStore() } /** - * @return $this + * @inheritDoc */ protected function _prepareCollection() { @@ -136,7 +141,6 @@ protected function _prepareCollection() ); } if ($store->getId()) { - //$collection->setStoreId($store->getId()); $collection->addStoreFilter($store); $collection->joinAttribute( 'name', @@ -187,8 +191,7 @@ protected function _prepareCollection() } /** - * @param \Magento\Backend\Block\Widget\Grid\Column $column - * @return $this + * @inheritDoc */ protected function _addColumnFilterToCollection($column) { @@ -208,8 +211,9 @@ protected function _addColumnFilterToCollection($column) } /** - * @return $this + * @inheritDoc * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Exception */ protected function _prepareColumns() { @@ -364,16 +368,11 @@ protected function _prepareColumns() ] ); - $block = $this->getLayout()->getBlock('grid.bottom.links'); - if ($block) { - $this->setChild('grid.bottom.links', $block); - } - return parent::_prepareColumns(); } /** - * @return $this + * @inheritDoc */ protected function _prepareMassaction() { @@ -425,7 +424,7 @@ protected function _prepareMassaction() } /** - * @return string + * @inheritDoc */ public function getGridUrl() { @@ -433,8 +432,7 @@ public function getGridUrl() } /** - * @param \Magento\Catalog\Model\Product|\Magento\Framework\DataObject $row - * @return string + * @inheritDoc */ public function getRowUrl($row) { diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php index 063503682f4db..f5d0ec7da617e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -19,6 +19,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; use Magento\Backend\Block\DataProviders\ImageUploadConfig as ImageUploadConfigDataProvider; +use Magento\MediaStorage\Helper\File\Storage\Database; /** * Block for gallery content. @@ -50,25 +51,34 @@ class Content extends \Magento\Backend\Block\Widget */ private $imageUploadConfigDataProvider; + /** + * @var Database + */ + private $fileStorageDatabase; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig * @param array $data * @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider + * @param Database $fileStorageDatabase */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, array $data = [], - ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null + ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null, + Database $fileStorageDatabase = null ) { $this->_jsonEncoder = $jsonEncoder; $this->_mediaConfig = $mediaConfig; parent::__construct($context, $data); $this->imageUploadConfigDataProvider = $imageUploadConfigDataProvider ?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class); + $this->fileStorageDatabase = $fileStorageDatabase + ?: ObjectManager::getInstance()->get(Database::class); } /** @@ -164,6 +174,13 @@ public function getImagesJson() $images = $this->sortImagesByPosition($value['images']); foreach ($images as &$image) { $image['url'] = $this->_mediaConfig->getMediaUrl($image['file']); + if ($this->fileStorageDatabase->checkDbUsage() && + !$mediaDir->isFile($this->_mediaConfig->getMediaPath($image['file'])) + ) { + $this->fileStorageDatabase->saveFileToFilesystem( + $this->_mediaConfig->getMediaPath($image['file']) + ); + } try { $fileHandler = $mediaDir->stat($this->_mediaConfig->getMediaPath($image['file'])); $image['size'] = $fileHandler['size']; @@ -187,9 +204,12 @@ public function getImagesJson() private function sortImagesByPosition($images) { if (is_array($images)) { - usort($images, function ($imageA, $imageB) { - return ($imageA['position'] < $imageB['position']) ? -1 : 1; - }); + usort( + $images, + function ($imageA, $imageB) { + return ($imageA['position'] < $imageB['position']) ? -1 : 1; + } + ); } return $images; } diff --git a/app/code/Magento/Catalog/Block/Product/Gallery.php b/app/code/Magento/Catalog/Block/Product/Gallery.php index e7c7b81ec29c9..54f848a92e958 100644 --- a/app/code/Magento/Catalog/Block/Product/Gallery.php +++ b/app/code/Magento/Catalog/Block/Product/Gallery.php @@ -16,6 +16,8 @@ use Magento\Framework\Data\Collection; /** + * Product gallery block + * * @api * @since 100.0.2 */ @@ -43,6 +45,8 @@ public function __construct( } /** + * Prepare layout + * * @return $this */ protected function _prepareLayout() @@ -52,6 +56,8 @@ protected function _prepareLayout() } /** + * Get product + * * @return Product */ public function getProduct() @@ -60,6 +66,8 @@ public function getProduct() } /** + * Get gallery collection + * * @return Collection */ public function getGalleryCollection() @@ -68,6 +76,8 @@ public function getGalleryCollection() } /** + * Get current image + * * @return Image|null */ public function getCurrentImage() @@ -85,6 +95,8 @@ public function getCurrentImage() } /** + * Get image url + * * @return string */ public function getImageUrl() @@ -93,6 +105,8 @@ public function getImageUrl() } /** + * Get image file + * * @return mixed */ public function getImageFile() @@ -115,7 +129,7 @@ public function getImageWidth() if ($size[0] > 600) { return 600; } else { - return $size[0]; + return (int) $size[0]; } } } @@ -124,6 +138,8 @@ public function getImageWidth() } /** + * Get previous image + * * @return Image|false */ public function getPreviousImage() @@ -143,6 +159,8 @@ public function getPreviousImage() } /** + * Get next image + * * @return Image|false */ public function getNextImage() @@ -166,6 +184,8 @@ public function getNextImage() } /** + * Get previous image url + * * @return false|string */ public function getPreviousImageUrl() @@ -178,6 +198,8 @@ public function getPreviousImageUrl() } /** + * Get next image url + * * @return false|string */ public function getNextImageUrl() diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index c1d79894162ae..144cb682e2d22 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -373,7 +373,7 @@ public function getAddToCartPostParams(Product $product) return [ 'action' => $url, 'data' => [ - 'product' => $product->getEntityId(), + 'product' => (int) $product->getEntityId(), ActionInterface::PARAM_NAME_URL_ENCODED => $this->urlHelper->getEncodedUrl($url), ] ]; diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php index 6de70bb971367..24811d61a7715 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php @@ -141,6 +141,7 @@ public function getIdentities() { $identities = []; foreach ($this->getItems() as $item) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $identities = array_merge($identities, $item->getIdentities()); } return $identities; diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index 24822447ae915..fa1beaf6e0ea8 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -264,6 +264,7 @@ public function getIdentities() { $identities = []; foreach ($this->getItems() as $item) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $identities = array_merge($identities, $item->getIdentities()); } return $identities; diff --git a/app/code/Magento/Catalog/Block/Product/View.php b/app/code/Magento/Catalog/Block/Product/View.php index fa03f72a4ba5c..942f6961c6eb9 100644 --- a/app/code/Magento/Catalog/Block/Product/View.php +++ b/app/code/Magento/Catalog/Block/Product/View.php @@ -169,8 +169,7 @@ public function getAddToCartUrl($product, $additional = []) } /** - * Get JSON encoded configuration array which can be used for JS dynamic - * price calculation depending on product options + * Get JSON encoded configuration which can be used for JS dynamic price calculation depending on product options * * @return string */ @@ -188,24 +187,25 @@ public function getJsonConfig() } $tierPrices = []; - $tierPricesList = $product->getPriceInfo()->getPrice('tier_price')->getTierPriceList(); + $priceInfo = $product->getPriceInfo(); + $tierPricesList = $priceInfo->getPrice('tier_price')->getTierPriceList(); foreach ($tierPricesList as $tierPrice) { - $tierPrices[] = $tierPrice['price']->getValue(); + $tierPrices[] = $tierPrice['price']->getValue() * 1; } $config = [ - 'productId' => $product->getId(), + 'productId' => (int)$product->getId(), 'priceFormat' => $this->_localeFormat->getPriceFormat(), 'prices' => [ 'oldPrice' => [ - 'amount' => $product->getPriceInfo()->getPrice('regular_price')->getAmount()->getValue(), + 'amount' => $priceInfo->getPrice('regular_price')->getAmount()->getValue() * 1, 'adjustments' => [] ], 'basePrice' => [ - 'amount' => $product->getPriceInfo()->getPrice('final_price')->getAmount()->getBaseAmount(), + 'amount' => $priceInfo->getPrice('final_price')->getAmount()->getBaseAmount() * 1, 'adjustments' => [] ], 'finalPrice' => [ - 'amount' => $product->getPriceInfo()->getPrice('final_price')->getAmount()->getValue(), + 'amount' => $priceInfo->getPrice('final_price')->getAmount()->getValue() * 1, 'adjustments' => [] ] ], @@ -262,6 +262,7 @@ public function isStartCustomization() /** * Get default qty - either as preconfigured, or as 1. + * * Also restricts it by minimal qty. * * @param null|\Magento\Catalog\Model\Product $product @@ -323,10 +324,7 @@ public function getQuantityValidators() public function getIdentities() { $identities = $this->getProduct()->getIdentities(); - $category = $this->_coreRegistry->registry('current_category'); - if ($category) { - $identities[] = Category::CACHE_TAG . '_' . $category->getId(); - } + return $identities; } diff --git a/app/code/Magento/Catalog/Block/Product/View/Gallery.php b/app/code/Magento/Catalog/Block/Product/View/Gallery.php index 8b98fbdc8f7ef..b6a58c07c428b 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Gallery.php +++ b/app/code/Magento/Catalog/Block/Product/View/Gallery.php @@ -137,16 +137,18 @@ public function getGalleryImagesJson() $imagesItems = []; /** @var DataObject $image */ foreach ($this->getGalleryImages() as $image) { - $imageItem = new DataObject([ - 'thumb' => $image->getData('small_image_url'), - 'img' => $image->getData('medium_image_url'), - 'full' => $image->getData('large_image_url'), - 'caption' => ($image->getLabel() ?: $this->getProduct()->getName()), - 'position' => $image->getData('position'), - 'isMain' => $this->isMainImage($image), - 'type' => str_replace('external-', '', $image->getMediaType()), - 'videoUrl' => $image->getVideoUrl(), - ]); + $imageItem = new DataObject( + [ + 'thumb' => $image->getData('small_image_url'), + 'img' => $image->getData('medium_image_url'), + 'full' => $image->getData('large_image_url'), + 'caption' => ($image->getLabel() ?: $this->getProduct()->getName()), + 'position' => $image->getData('position'), + 'isMain' => $this->isMainImage($image), + 'type' => str_replace('external-', '', $image->getMediaType()), + 'videoUrl' => $image->getVideoUrl(), + ] + ); foreach ($this->getGalleryImagesConfig()->getItems() as $imageConfig) { $imageItem->setData( $imageConfig->getData('json_object_key'), diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php index 9c5c4562f3b43..1dcbf60db15c3 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php @@ -15,8 +15,9 @@ use Magento\Catalog\Pricing\Price\CustomOptionPriceInterface; /** - * Product aoptions section abstract block. + * Product options section abstract block. * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index ff7311e931755..820f2e5e5b8cd 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,7 +7,11 @@ use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +/** + * Class Upload + */ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** @@ -23,6 +26,16 @@ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterf */ protected $resultRawFactory; + /** + * @var array + */ + private $allowedMimeTypes = [ + 'jpg' => 'image/jpg', + 'jpeg' => 'image/jpeg', + 'gif' => 'image/png', + 'png' => 'image/gif' + ]; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory @@ -36,6 +49,8 @@ public function __construct( } /** + * Upload image(s) to the product gallery. + * * @return \Magento\Framework\Controller\Result\Raw */ public function execute() @@ -45,7 +60,12 @@ public function execute() \Magento\MediaStorage\Model\File\Uploader::class, ['fileId' => 'image'] ); - $uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png']); + $uploader->setAllowedExtensions($this->getAllowedExtensions()); + + if (!$uploader->checkMimeType($this->getAllowedMimeTypes())) { + throw new LocalizedException(__('Disallowed File Type.')); + } + /** @var \Magento\Framework\Image\Adapter\AdapterInterface $imageAdapter */ $imageAdapter = $this->_objectManager->get(\Magento\Framework\Image\AdapterFactory::class)->create(); $uploader->addValidateCallback('catalog_product_image', $imageAdapter, 'validateUploadFile'); @@ -78,4 +98,24 @@ public function execute() $response->setContents(json_encode($result)); return $response; } + + /** + * Get the set of allowed file extensions. + * + * @return array + */ + private function getAllowedExtensions() + { + return array_keys($this->allowedMimeTypes); + } + + /** + * Get the set of allowed mime types. + * + * @return array + */ + private function getAllowedMimeTypes() + { + return array_values($this->allowedMimeTypes); + } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 188b0b22f33bf..49165c85f85d7 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -46,6 +46,8 @@ public function prepareProductAttributes(Product $product, array $productData, a } /** + * Reset "Use Config Settings" to false in product data. + * * @param Product $product * @param string $attributeCode * @param array $productData @@ -62,6 +64,8 @@ private function prepareConfigData(Product $product, string $attributeCode, arra } /** + * Prepare default attribute data for product. + * * @param array $attributeList * @param string $attributeCode * @param array $productData @@ -73,7 +77,7 @@ private function prepareDefaultData(array $attributeList, string $attributeCode, /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ $attribute = $attributeList[$attributeCode]; $attributeType = $attribute->getBackendType(); - // For non-numberic types set the attributeValue to 'false' to trigger their removal from the db + // For non-numeric types set the attributeValue to 'false' to trigger their removal from the db if ($attributeType === 'varchar' || $attributeType === 'text' || $attributeType === 'datetime') { $attribute->setIsRequired(false); $productData[$attributeCode] = false; @@ -86,6 +90,8 @@ private function prepareDefaultData(array $attributeList, string $attributeCode, } /** + * Check, whether attribute should not be updated. + * * @param Product $product * @param array $useDefaults * @param string $attribute diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php index f5c3171a3fe90..8b854361fd4ef 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php @@ -6,14 +6,72 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\ViewModel\Product\Checker\AddToCompareAvailability; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\View\Result\PageFactory; /** * Add item to compare list action. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Add extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface { + /** + * @var AddToCompareAvailability + */ + private $compareAvailability; + + /** + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Catalog\Model\Product\Compare\ItemFactory $compareItemFactory + * @param \Magento\Catalog\Model\ResourceModel\Product\Compare\Item\CollectionFactory $itemCollectionFactory + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Customer\Model\Visitor $customerVisitor + * @param \Magento\Catalog\Model\Product\Compare\ListCompare $catalogProductCompareList + * @param \Magento\Catalog\Model\Session $catalogSession + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param Validator $formKeyValidator + * @param PageFactory $resultPageFactory + * @param ProductRepositoryInterface $productRepository + * @param AddToCompareAvailability|null $compareAvailability + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Framework\App\Action\Context $context, + \Magento\Catalog\Model\Product\Compare\ItemFactory $compareItemFactory, + \Magento\Catalog\Model\ResourceModel\Product\Compare\Item\CollectionFactory $itemCollectionFactory, + \Magento\Customer\Model\Session $customerSession, + \Magento\Customer\Model\Visitor $customerVisitor, + \Magento\Catalog\Model\Product\Compare\ListCompare $catalogProductCompareList, + \Magento\Catalog\Model\Session $catalogSession, + \Magento\Store\Model\StoreManagerInterface $storeManager, + Validator $formKeyValidator, + PageFactory $resultPageFactory, + ProductRepositoryInterface $productRepository, + AddToCompareAvailability $compareAvailability = null + ) { + parent::__construct( + $context, + $compareItemFactory, + $itemCollectionFactory, + $customerSession, + $customerVisitor, + $catalogProductCompareList, + $catalogSession, + $storeManager, + $formKeyValidator, + $resultPageFactory, + $productRepository + ); + + $this->compareAvailability = $compareAvailability + ?: $this->_objectManager->get(AddToCompareAvailability::class); + } + /** * Add item to compare list. * @@ -36,7 +94,7 @@ public function execute() $product = null; } - if ($product && $product->isSalable()) { + if ($product && $this->compareAvailability->isAvailableForCompare($product)) { $this->_catalogProductCompareList->addProduct($product); $productName = $this->_objectManager->get( \Magento\Framework\Escaper::class diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php index acf0f1b754c12..f5d56dc9e6b0e 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php @@ -6,6 +6,7 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; @@ -17,12 +18,13 @@ class Remove extends \Magento\Catalog\Controller\Product\Compare implements Http /** * Remove item from compare list. * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @return \Magento\Framework\Controller\ResultInterface */ public function execute() { $productId = (int)$this->getRequest()->getParam('product'); - if ($productId) { + if ($this->_formKeyValidator->validate($this->getRequest()) && $productId) { $storeId = $this->_storeManager->getStore()->getId(); try { /** @var \Magento\Catalog\Model\Product $product */ @@ -31,7 +33,7 @@ public function execute() $product = null; } - if ($product && $product->isSalable()) { + if ($product && (int)$product->getStatus() !== Status::STATUS_DISABLED) { /** @var $item \Magento\Catalog\Model\Product\Compare\Item */ $item = $this->_compareItemFactory->create(); if ($this->_customerSession->isLoggedIn()) { diff --git a/app/code/Magento/Catalog/Helper/Product.php b/app/code/Magento/Catalog/Helper/Product.php index c0d5d1d9e4503..73b5e4af78d41 100644 --- a/app/code/Magento/Catalog/Helper/Product.php +++ b/app/code/Magento/Catalog/Helper/Product.php @@ -14,6 +14,7 @@ /** * Catalog category helper * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Product extends \Magento\Framework\Url\Helper\Data { @@ -268,6 +269,8 @@ public function getThumbnailUrl($product) } /** + * Retrieve email to friend url + * * @param ModelProduct $product * @return string */ @@ -282,6 +285,8 @@ public function getEmailToFriendUrl($product) } /** + * Get statuses + * * @return array */ public function getStatuses() @@ -476,6 +481,7 @@ public function initProduct($productId, $controller, $params = null) /** * Prepares product options by buyRequest: retrieves values and assigns them as default. + * * Also parses and adds product management related values - e.g. qty * * @param ModelProduct $product @@ -493,6 +499,7 @@ public function prepareProductOptions($product, $buyRequest) /** * Process $buyRequest and sets its options before saving configuration to some product item. + * * This method is used to attach additional parameters to processed buyRequest. * * $params holds parameters of what operation must be performed: @@ -541,8 +548,6 @@ public function addParamsToBuyRequest($buyRequest, $params) /** * Set flag that shows if Magento has to check product to be saleable (enabled and/or inStock) * - * For instance, during order creation in the backend admin has ability to add any products to order - * * @param bool $skipSaleableCheck * @return Product */ diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index f0e7837392cea..716cc6a2d76c2 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -623,11 +623,13 @@ public function getUrl() return $this->getData('url'); } - $rewrite = $this->urlFinder->findOneByData([ - UrlRewrite::ENTITY_ID => $this->getId(), - UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, - UrlRewrite::STORE_ID => $this->getStoreId(), - ]); + $rewrite = $this->urlFinder->findOneByData( + [ + UrlRewrite::ENTITY_ID => $this->getId(), + UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::STORE_ID => $this->getStoreId(), + ] + ); if ($rewrite) { $this->setData('url', $this->getUrlInstance()->getDirectUrl($rewrite->getRequestPath())); Profiler::stop('REWRITE: ' . __METHOD__); diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index cd450e26cd832..6a035a4681a54 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Model\Category\Attribute\Backend; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\File\Uploader; /** * Catalog category image attribute backend model @@ -70,7 +71,8 @@ public function __construct( /** * Gets image name from $value array. - * Will return empty string in a case when $value is not an array + * + * Will return empty string in a case when $value is not an array. * * @param array $value Attribute value * @return string @@ -85,8 +87,28 @@ private function getUploadedImageName($value) } /** - * Avoiding saving potential upload data to DB - * Will set empty image attribute value if image was not uploaded + * Check that image name exists in catalog/category directory and return new image name if it already exists. + * + * @param string $imageName + * @return string + */ + private function checkUniqueImageName(string $imageName): string + { + $imageUploader = $this->getImageUploader(); + $mediaDirectory = $this->_filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $imageAbsolutePath = $mediaDirectory->getAbsolutePath( + $imageUploader->getBasePath() . DIRECTORY_SEPARATOR . $imageName + ); + + $imageName = Uploader::getNewFilename($imageAbsolutePath); + + return $imageName; + } + + /** + * Avoiding saving potential upload data to DB. + * + * Will set empty image attribute value if image was not uploaded. * * @param \Magento\Framework\DataObject $object * @return $this @@ -103,6 +125,7 @@ public function beforeSave($object) } if ($imageName = $this->getUploadedImageName($value)) { + $imageName = $this->checkUniqueImageName($imageName); $object->setData($this->additionalData . $attributeName, $value); $object->setData($attributeName, $imageName); } elseif (!is_string($value)) { @@ -113,6 +136,8 @@ public function beforeSave($object) } /** + * Get Instance of Category Image Uploader. + * * @return \Magento\Catalog\Model\ImageUploader * * @deprecated 101.0.0 @@ -153,9 +178,11 @@ private function fileResidesOutsideCategoryDir($value) $fileUrl = ltrim($value[0]['url'], '/'); $baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA); - $usingPathRelativeToBase = strpos($fileUrl, $baseMediaDir) === 0; + if (!$baseMediaDir) { + return false; + } - return $usingPathRelativeToBase; + return strpos($fileUrl, $baseMediaDir) === 0; } /** diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Sortby.php b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Sortby.php index 97bc00bc7dd64..4dda2fe5786e3 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Sortby.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Sortby.php @@ -40,7 +40,7 @@ protected function _getCatalogConfig() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAllOptions() { @@ -49,7 +49,7 @@ public function getAllOptions() foreach ($this->_getCatalogConfig()->getAttributesUsedForSortBy() as $attribute) { $this->_options[] = [ 'label' => __($attribute['frontend_label']), - 'value' => $attribute['attribute_code'], + 'value' => $attribute['attribute_code'] ]; } } diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php index 18fac3d09958c..8d7b453a18368 100644 --- a/app/code/Magento/Catalog/Model/Category/DataProvider.php +++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php @@ -272,10 +272,13 @@ private function addUseDefaultValueCheckbox(Category $category, array $meta) */ public function prepareMeta($meta) { - $meta = array_replace_recursive($meta, $this->prepareFieldsMeta( - $this->getFieldsMap(), - $this->getAttributesMeta($this->eavConfig->getEntityType('catalog_category')) - )); + $meta = array_replace_recursive( + $meta, + $this->prepareFieldsMeta( + $this->getFieldsMap(), + $this->getAttributesMeta($this->eavConfig->getEntityType('catalog_category')) + ) + ); return $meta; } @@ -361,6 +364,9 @@ public function getAttributesMeta(Type $entityType) } if ($attribute->usesSource()) { $meta[$code]['options'] = $attribute->getSource()->getAllOptions(); + foreach ($meta[$code]['options'] as &$option) { + $option['__disableTmpl'] = true; + } } } diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 9715bb2b1616e..d77f472c6be90 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -43,6 +43,11 @@ class FileInfo */ private $baseDirectory; + /** + * @var ReadInterface + */ + private $pubDirectory; + /** * @param Filesystem $filesystem * @param Mime $mime @@ -82,6 +87,20 @@ private function getBaseDirectory() return $this->baseDirectory; } + /** + * Get Pub Directory read instance + * + * @return ReadInterface + */ + private function getPubDirectory() + { + if (!isset($this->pubDirectory)) { + $this->pubDirectory = $this->filesystem->getDirectoryRead(DirectoryList::PUB); + } + + return $this->pubDirectory; + } + /** * Retrieve MIME type of requested file * @@ -135,7 +154,7 @@ private function getFilePath($fileName) { $filePath = ltrim($fileName, '/'); - $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath(); + $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); $isFileNameBeginsWithMediaDirectoryPath = $this->isBeginsWithMediaDirectoryPath($fileName); // if the file is not using a relative path, it resides in the catalog/category media directory @@ -160,8 +179,8 @@ public function isBeginsWithMediaDirectoryPath($fileName) { $filePath = ltrim($fileName, '/'); - $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath(); - $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, $mediaDirectoryRelativeSubpath) === 0; + $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); + $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, (string) $mediaDirectoryRelativeSubpath) === 0; return $isFileNameBeginsWithMediaDirectoryPath; } @@ -169,14 +188,22 @@ public function isBeginsWithMediaDirectoryPath($fileName) /** * Get media directory subpath relative to base directory path * + * @param string $filePath * @return string */ - private function getMediaDirectoryPathRelativeToBaseDirectoryPath() + private function getMediaDirectoryPathRelativeToBaseDirectoryPath(string $filePath = '') { - $baseDirectoryPath = $this->getBaseDirectory()->getAbsolutePath(); + $baseDirectory = $this->getBaseDirectory(); + $baseDirectoryPath = $baseDirectory->getAbsolutePath(); $mediaDirectoryPath = $this->getMediaDirectory()->getAbsolutePath(); + $pubDirectoryPath = $this->getPubDirectory()->getAbsolutePath(); $mediaDirectoryRelativeSubpath = substr($mediaDirectoryPath, strlen($baseDirectoryPath)); + $pubDirectory = $baseDirectory->getRelativePath($pubDirectoryPath); + + if (strpos($mediaDirectoryRelativeSubpath, $pubDirectory) === 0 && strpos($filePath, $pubDirectory) !== 0) { + $mediaDirectoryRelativeSubpath = substr($mediaDirectoryRelativeSubpath, strlen($pubDirectory)); + } return $mediaDirectoryRelativeSubpath; } diff --git a/app/code/Magento/Catalog/Model/Category/StoreCategories.php b/app/code/Magento/Catalog/Model/Category/StoreCategories.php new file mode 100644 index 0000000000000..0d7afcd4a5000 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Category/StoreCategories.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Category; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Store\Api\GroupRepositoryInterface; + +/** + * Fetcher for associated with store group categories. + */ +class StoreCategories +{ + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var GroupRepositoryInterface + */ + private $groupRepository; + + /** + * @param CategoryRepositoryInterface $categoryRepository + * @param GroupRepositoryInterface $groupRepository + */ + public function __construct( + CategoryRepositoryInterface $categoryRepository, + GroupRepositoryInterface $groupRepository + ) { + $this->categoryRepository = $categoryRepository; + $this->groupRepository = $groupRepository; + } + + /** + * Get all category ids for store. + * + * @param int|null $storeGroupId + * @return int[] + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getCategoryIds(?int $storeGroupId = null): array + { + $rootCategoryId = $storeGroupId + ? $this->groupRepository->get($storeGroupId)->getRootCategoryId() + : Category::TREE_ROOT_ID; + /** @var Category $rootCategory */ + $rootCategory = $this->categoryRepository->get($rootCategoryId); + $categoriesIds = array_map( + function ($value) { + return (int) $value; + }, + (array) $rootCategory->getAllChildren(true) + ); + + return $categoriesIds; + } +} diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php index 225aefb4035b4..e7c755b379b91 100644 --- a/app/code/Magento/Catalog/Model/CategoryList.php +++ b/app/code/Magento/Catalog/Model/CategoryList.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Model; use Magento\Catalog\Api\CategoryListInterface; @@ -50,7 +53,7 @@ class CategoryList implements CategoryListInterface * @param JoinProcessorInterface $extensionAttributesJoinProcessor * @param CategorySearchResultsInterfaceFactory $categorySearchResultsFactory * @param CategoryRepositoryInterface $categoryRepository - * @param CollectionProcessorInterface $collectionProcessor + * @param CollectionProcessorInterface|null $collectionProcessor */ public function __construct( CollectionFactory $categoryCollectionFactory, @@ -74,12 +77,13 @@ public function getList(SearchCriteriaInterface $searchCriteria) /** @var Collection $collection */ $collection = $this->categoryCollectionFactory->create(); $this->extensionAttributesJoinProcessor->process($collection); - $this->collectionProcessor->process($searchCriteria, $collection); $items = []; - foreach ($collection->getAllIds() as $id) { - $items[] = $this->categoryRepository->get($id); + foreach ($collection->getData() as $categoryData) { + $items[] = $this->categoryRepository->get( + $categoryData[$collection->getEntity()->getIdFieldName()] + ); } /** @var CategorySearchResultsInterface $searchResult */ diff --git a/app/code/Magento/Catalog/Model/CategoryManagement.php b/app/code/Magento/Catalog/Model/CategoryManagement.php index fadb4c0c84745..bf6f26ebfb10a 100644 --- a/app/code/Magento/Catalog/Model/CategoryManagement.php +++ b/app/code/Magento/Catalog/Model/CategoryManagement.php @@ -1,14 +1,14 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model; -use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; - +/** + * Handles the category tree. + */ class CategoryManagement implements \Magento\Catalog\Api\CategoryManagementInterface { /** @@ -30,7 +30,7 @@ class CategoryManagement implements \Magento\Catalog\Api\CategoryManagementInter * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory */ private $categoriesFactory; - + /** * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository * @param Category\Tree $categoryTree @@ -47,7 +47,14 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * + * @param int $rootCategoryId + * @param int $depth + * @return \Magento\Catalog\Api\Data\CategoryTreeInterface + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getTree($rootCategoryId = null, $depth = null) { @@ -99,7 +106,15 @@ private function getTopLevelCategory() } /** - * {@inheritdoc} + * @inheritdoc + * + * @param int $categoryId + * @param int $parentId + * @param int $afterId + * @return bool + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function move($categoryId, $parentId, $afterId = null) { @@ -112,8 +127,9 @@ public function move($categoryId, $parentId, $afterId = null) $lastId = array_pop($categoryIds); $afterId = ($afterId === null || $afterId > $lastId) ? $lastId : $afterId; } - - if (strpos($parentCategory->getPath(), $model->getPath()) === 0) { + $parentPath = $parentCategory->getPath(); + $path = $model->getPath(); + if ($path && strpos($parentPath, $path) === 0) { throw new \Magento\Framework\Exception\LocalizedException( __('Operation do not allow to move a parent category to any of children category') ); @@ -127,7 +143,7 @@ public function move($categoryId, $parentId, $afterId = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCount() { diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index b5ca0895d6d1a..825823276261f 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Model; +use Magento\Framework\File\Uploader; + /** * Catalog image uploader */ @@ -199,7 +201,14 @@ public function moveFileFromTmp($imageName) $baseTmpPath = $this->getBaseTmpPath(); $basePath = $this->getBasePath(); - $baseImagePath = $this->getFilePath($basePath, $imageName); + $baseImagePath = $this->getFilePath( + $basePath, + Uploader::getNewFileName( + $this->mediaDirectory->getAbsolutePath( + $this->getFilePath($basePath, $imageName) + ) + ) + ); $baseTmpImagePath = $this->getFilePath($baseTmpPath, $imageName); try { diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php index 3b63a90d4c3ae..a0acacd4dfd2f 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/AbstractAction.php @@ -169,8 +169,7 @@ protected function _reindex($storeId, array $changedIds = []) } /** - * Retrieve Product Type Instances - * as key - type code, value - instance model + * Retrieve Product Type Instances as key - type code, value - instance model * * @return array */ @@ -213,17 +212,19 @@ protected function _updateRelationProducts($storeId, $productIds = null) ) { $columns = $this->_productIndexerHelper->getFlatColumns(); $fieldList = array_keys($columns); - unset($columns['entity_id']); - unset($columns['child_id']); - unset($columns['is_child']); + unset( + $columns['entity_id'], + $columns['child_id'], + $columns['is_child'] + ); /** @var $select \Magento\Framework\DB\Select */ $select = $this->_connection->select()->from( ['t' => $this->_productIndexerHelper->getTable($relation->getTable())], - [$relation->getChildFieldName(), new \Zend_Db_Expr('1')] + ['entity_table.entity_id', $relation->getChildFieldName(), new \Zend_Db_Expr('1')] )->join( ['entity_table' => $this->_connection->getTableName('catalog_product_entity')], - 'entity_table.' . $metadata->getLinkField() . 't.' . $relation->getParentFieldName(), - [$relation->getParentFieldName() => 'entity_table.entity_id'] + "entity_table.{$metadata->getLinkField()} = t.{$relation->getParentFieldName()}", + [] )->join( ['e' => $this->_productIndexerHelper->getFlatTableName($storeId)], "e.entity_id = t.{$relation->getChildFieldName()}", @@ -232,10 +233,10 @@ protected function _updateRelationProducts($storeId, $productIds = null) if ($relation->getWhere() !== null) { $select->where($relation->getWhere()); } - if ($productIds !== null) { + if (!empty($productIds)) { $cond = [ $this->_connection->quoteInto("{$relation->getChildFieldName()} IN(?)", $productIds), - $this->_connection->quoteInto("entity_table.entity_id IN(?)", $productIds), + $this->_connection->quoteInto('entity_table.entity_id IN(?)', $productIds), ]; $select->where(implode(' OR ', $cond)); @@ -253,10 +254,12 @@ protected function _updateRelationProducts($storeId, $productIds = null) * * @param int $storeId * @return \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _cleanRelationProducts($storeId) { - if (!$this->_productIndexerHelper->isAddChildData()) { + if (!$this->_productIndexerHelper->isAddChildData() || !$this->_isFlatTableExists($storeId)) { return $this; } @@ -273,15 +276,11 @@ protected function _cleanRelationProducts($storeId) $select = $this->_connection->select()->distinct( true )->from( - ['t' => $this->_productIndexerHelper->getTable($relation->getTable())], - [] - )->join( - ['entity_table' => $this->_connection->getTableName('catalog_product_entity')], - 'entity_table.' . $metadata->getLinkField() . 't.' . $relation->getParentFieldName(), - [$relation->getParentFieldName() => 'entity_table.entity_id'] + $this->_productIndexerHelper->getTable($relation->getTable()), + $relation->getParentFieldName() ); $joinLeftCond = [ - "e.entity_id = entity_table.entity_id", + "e.{$metadata->getLinkField()} = t.{$relation->getParentFieldName()}", "e.child_id = t.{$relation->getChildFieldName()}", ]; if ($relation->getWhere() !== null) { @@ -302,7 +301,7 @@ protected function _cleanRelationProducts($storeId) 'e.is_child = ?', 1 )->where( - 'e.entity_id IN(?)', + "e.{$metadata->getLinkField()} IN(?)", $entitySelect )->where( "t.{$relation->getChildFieldName()} IS NULL" @@ -335,6 +334,8 @@ protected function _isFlatTableExists($storeId) } /** + * Get Metadata Pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php index ad734b96d59d7..c4d807667bfbc 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php @@ -9,6 +9,7 @@ use Magento\Framework\App\ResourceConnection; use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; /** @@ -31,19 +32,28 @@ class Eraser */ protected $storeManager; + /** + * @var MetadataPool + */ + private $metadataPool; + /** * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Catalog\Helper\Product\Flat\Indexer $productHelper * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param MetadataPool|null $metadataPool */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Catalog\Helper\Product\Flat\Indexer $productHelper, - \Magento\Store\Model\StoreManagerInterface $storeManager + \Magento\Store\Model\StoreManagerInterface $storeManager, + MetadataPool $metadataPool = null ) { $this->productIndexerHelper = $productHelper; $this->connection = $resource->getConnection(); $this->storeManager = $storeManager; + $this->metadataPool = $metadataPool ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(MetadataPool::class); } /** @@ -81,17 +91,24 @@ public function removeDisabledProducts(array &$ids, $storeId) /* @var $statusAttribute \Magento\Eav\Model\Entity\Attribute */ $statusAttribute = $this->productIndexerHelper->getAttribute('status'); + /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */ + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $select = $this->getSelectForProducts($ids); $select->joinLeft( ['status_global_attr' => $statusAttribute->getBackendTable()], ' status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() - . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID + . ' AND status_global_attr.' . $statusAttribute->getEntityIdField() . '=' + . 'product_table.' . $metadata->getLinkField(), [] ); $select->joinLeft( ['status_attr' => $statusAttribute->getBackendTable()], ' status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() - . ' AND status_attr.store_id = ' . $storeId, + . ' AND status_attr.store_id = ' . $storeId + . ' AND status_attr.' . $statusAttribute->getEntityIdField() . '=' + . 'product_table.' . $metadata->getLinkField(), [] ); $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_DISABLED); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php index 140c8874b5db3..0897ae0d74c0b 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php @@ -6,9 +6,12 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat; use Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterfaceFactory; +use Magento\Store\Model\Store; /** * Class TableBuilder + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TableBuilder { @@ -83,6 +86,7 @@ public function build($storeId, $changedIds, $valueFieldSuffix) //Create list of temporary tables based on available attributes attributes $valueTables = []; foreach ($temporaryEavAttributes as $tableName => $columns) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $valueTables = array_merge( $valueTables, $this->_createTemporaryTable($this->_getTemporaryTableName($tableName), $columns, $valueFieldSuffix) @@ -272,75 +276,71 @@ protected function _fillTemporaryTable( $valueFieldSuffix, $storeId ) { - $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); if (!empty($tableColumns)) { - $columnsChunks = array_chunk( - $tableColumns, - Action\Indexer::ATTRIBUTES_CHUNK_SIZE, - true - ); + $columnsChunks = array_chunk($tableColumns, Action\Indexer::ATTRIBUTES_CHUNK_SIZE / 2, true); + + $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity'); + $entityTemporaryTableName = $this->_getTemporaryTableName($entityTableName); + $temporaryTableName = $this->_getTemporaryTableName($tableName); + $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix; + $attributeOptionValueTableName = $this->_productIndexerHelper->getTable('eav_attribute_option_value'); + + $flatColumns = $this->_productIndexerHelper->getFlatColumns(); + $defaultStoreId = Store::DEFAULT_STORE_ID; + $linkField = $this->getMetadataPool() + ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + foreach ($columnsChunks as $columnsList) { $select = $this->_connection->select(); $selectValue = $this->_connection->select(); - $entityTableName = $this->_getTemporaryTableName( - $this->_productIndexerHelper->getTable('catalog_product_entity') - ); - $temporaryTableName = $this->_getTemporaryTableName($tableName); - $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix; - $keyColumn = array_unique([$metadata->getLinkField(), 'entity_id']); + $keyColumn = array_unique([$linkField, 'entity_id']); + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $columns = array_merge($keyColumn, array_keys($columnsList)); $valueColumns = $keyColumn; - $flatColumns = $this->_productIndexerHelper->getFlatColumns(); $iterationNum = 1; - $select->from(['et' => $entityTableName], $keyColumn) - ->join( - ['e' => $this->resource->getTableName('catalog_product_entity')], - 'e.entity_id = et.entity_id', - [] - ); + $select->from(['et' => $entityTemporaryTableName], $keyColumn) + ->join(['e' => $entityTableName], 'e.entity_id = et.entity_id', []); $selectValue->from(['e' => $temporaryTableName], $keyColumn); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ foreach ($columnsList as $columnName => $attribute) { $countTableName = 't' . ($iterationNum++); - $joinCondition = sprintf( - 'e.%3$s = %1$s.%3$s' . - ' AND %1$s.attribute_id = %2$d' . - ' AND (%1$s.store_id = %4$d' . - ' OR %1$s.store_id = 0)', - $countTableName, - $attribute->getId(), - $metadata->getLinkField(), - $storeId - ); - + $joinCondition = 'e.%3$s = %1$s.%3$s AND %1$s.attribute_id = %2$d AND %1$s.store_id = %4$d'; $select->joinLeft( [$countTableName => $tableName], - $joinCondition, - [$columnName => 'value'] + sprintf($joinCondition, $countTableName, $attribute->getId(), $linkField, $defaultStoreId), + [] + )->joinLeft( + ['s' . $countTableName => $tableName], + sprintf($joinCondition, 's' . $countTableName, $attribute->getId(), $linkField, $storeId), + [] ); + $columnValue = $this->_connection->getIfNullSql( + 's' . $countTableName . '.value', + $countTableName . '.value' + ); + $select->columns([$columnName => $columnValue]); + if ($attribute->getFlatUpdateSelect($storeId) instanceof \Magento\Framework\DB\Select) { $attributeCode = $attribute->getAttributeCode(); $columnValueName = $attributeCode . $valueFieldSuffix; if (isset($flatColumns[$columnValueName])) { - $valueJoinCondition = sprintf( - 'e.%1$s = %2$s.option_id AND (%2$s.store_id = %3$d OR %2$s.store_id = 0)', - $attributeCode, - $countTableName, - $storeId - ); + $valueJoinCondition = 'e.%1$s = %2$s.option_id AND %2$s.store_id = %3$d'; $selectValue->joinLeft( - [ - $countTableName => $this->_productIndexerHelper->getTable( - 'eav_attribute_option_value' - ), - ], - $valueJoinCondition, - [$columnValueName => $countTableName . '.value'] + [$countTableName => $attributeOptionValueTableName], + sprintf($valueJoinCondition, $attributeCode, $countTableName, $defaultStoreId), + [] + )->joinLeft( + ['s' . $countTableName => $attributeOptionValueTableName], + sprintf($valueJoinCondition, $attributeCode, 's' . $countTableName, $storeId), + [] ); + + $selectValue->columns([$columnValueName => $columnValue]); $valueColumns[] = $columnValueName; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index 0f14ea6239eaf..754022b573d62 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -279,6 +279,7 @@ private function getBatchesForIndexer(string $typeId): BatchIterator $select = $connection->select(); $select->distinct(true); $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); + $select->where('type_id = ?', $typeId); return $this->batchQueryGenerator->generate( $this->getProductMetaData()->getIdentifierField(), diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index ea1d045e8fbb8..31fd7e73e87cf 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -501,6 +501,7 @@ protected function _construct() // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod /** * Get resource instance + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod * * @throws \Magento\Framework\Exception\LocalizedException * @return \Magento\Catalog\Model\ResourceModel\Product @@ -838,6 +839,7 @@ public function getStoreIds() } foreach ($websiteIds as $websiteId) { $websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds(); + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $storeIds = array_merge($storeIds, $websiteStores); } } @@ -1876,7 +1878,7 @@ public function formatUrlKey($str) } /** - * Save current attribute with code $code and assign new value + * Save current attribute with code $code and assign new value. * * @param string $code Attribute code * @param mixed $value New attribute value diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php index 663b7facf4257..f1943bc108878 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php @@ -7,8 +7,9 @@ namespace Magento\Catalog\Model\Product\Attribute\Backend\TierPrice; -use Magento\Framework\EntityManager\Operation\ExtensionInterface; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Locale\FormatInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Customer\Api\GroupManagementInterface; @@ -40,19 +41,26 @@ class UpdateHandler extends AbstractHandler */ private $tierPriceResource; + /** + * @var FormatInterface + */ + private $localeFormat; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $tierPriceResource + * @param FormatInterface|null $localeFormat */ public function __construct( StoreManagerInterface $storeManager, ProductAttributeRepositoryInterface $attributeRepository, GroupManagementInterface $groupManagement, MetadataPool $metadataPool, - Tierprice $tierPriceResource + Tierprice $tierPriceResource, + FormatInterface $localeFormat = null ) { parent::__construct($groupManagement); @@ -60,6 +68,7 @@ public function __construct( $this->attributeRepository = $attributeRepository; $this->metadataPoll = $metadataPool; $this->tierPriceResource = $tierPriceResource; + $this->localeFormat = $localeFormat ?: ObjectManager::getInstance()->get(FormatInterface::class); } /** @@ -125,8 +134,9 @@ private function updateValues(array $valuesToUpdate, array $oldValues): bool { $isChanged = false; foreach ($valuesToUpdate as $key => $value) { - if ((!empty($value['value']) && (float)$oldValues[$key]['price'] !== (float)$value['value']) - || $this->getPercentage($oldValues[$key]) !== $this->getPercentage($value) + if ((!empty($value['value']) + && (float)$oldValues[$key]['price'] !== $this->localeFormat->getNumber($value['value']) + ) || $this->getPercentage($oldValues[$key]) !== $this->getPercentage($value) ) { $price = new \Magento\Framework\DataObject( [ diff --git a/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php b/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php index d0c7103851499..57d08916bcd40 100644 --- a/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php +++ b/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php @@ -5,10 +5,13 @@ */ namespace Magento\Catalog\Model\Product\AttributeSet; +/** + * Attribute Set Options + */ class Options implements \Magento\Framework\Data\OptionSourceInterface { /** - * @var null|array + * @var array */ protected $options; @@ -25,7 +28,7 @@ public function __construct( } /** - * @return array|null + * @inheritDoc */ public function toOptionArray() { @@ -33,7 +36,15 @@ public function toOptionArray() $this->options = $this->collectionFactory->create() ->setEntityTypeFilter($this->product->getTypeId()) ->toOptionArray(); + + array_walk( + $this->options, + function (&$option) { + $option['__disableTmpl'] = true; + } + ); } + return $this->options; } } diff --git a/app/code/Magento/Catalog/Model/Product/Compare/ListCompare.php b/app/code/Magento/Catalog/Model/Product/Compare/ListCompare.php index 12dbaf0c29f4e..bfd36360ee559 100644 --- a/app/code/Magento/Catalog/Model/Product/Compare/ListCompare.php +++ b/app/code/Magento/Catalog/Model/Product/Compare/ListCompare.php @@ -5,7 +5,10 @@ */ namespace Magento\Catalog\Model\Product\Compare; +use Magento\Catalog\Model\ProductRepository; use Magento\Catalog\Model\ResourceModel\Product\Compare\Item\Collection; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; /** * Product Compare List Model @@ -51,6 +54,11 @@ class ListCompare extends \Magento\Framework\DataObject */ protected $_compareItemFactory; + /** + * @var ProductRepository + */ + private $productRepository; + /** * Constructor * @@ -60,6 +68,7 @@ class ListCompare extends \Magento\Framework\DataObject * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Customer\Model\Visitor $customerVisitor * @param array $data + * @param ProductRepository|null $productRepository */ public function __construct( \Magento\Catalog\Model\Product\Compare\ItemFactory $compareItemFactory, @@ -67,13 +76,15 @@ public function __construct( \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem, \Magento\Customer\Model\Session $customerSession, \Magento\Customer\Model\Visitor $customerVisitor, - array $data = [] + array $data = [], + ProductRepository $productRepository = null ) { $this->_compareItemFactory = $compareItemFactory; $this->_itemCollectionFactory = $itemCollectionFactory; $this->_catalogProductCompareItem = $catalogProductCompareItem; $this->_customerSession = $customerSession; $this->_customerVisitor = $customerVisitor; + $this->productRepository = $productRepository ?: ObjectManager::getInstance()->create(ProductRepository::class); parent::__construct($data); } @@ -82,6 +93,7 @@ public function __construct( * * @param int|\Magento\Catalog\Model\Product $product * @return $this + * @throws \Exception */ public function addProduct($product) { @@ -90,7 +102,7 @@ public function addProduct($product) $this->_addVisitorToItem($item); $item->loadByProduct($product); - if (!$item->getId()) { + if (!$item->getId() && $this->productExists($product)) { $item->addProductData($product); $item->save(); } @@ -98,6 +110,25 @@ public function addProduct($product) return $this; } + /** + * Check product exists. + * + * @param int|\Magento\Catalog\Model\Product $product + * @return bool + */ + private function productExists($product) + { + if ($product instanceof \Magento\Catalog\Model\Product && $product->getId()) { + return true; + } + try { + $product = $this->productRepository->getById((int)$product); + return !empty($product->getId()); + } catch (NoSuchEntityException $e) { + return false; + } + } + /** * Add products to compare list * diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php index 0912324745360..f1d27c38e9456 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php @@ -157,6 +157,7 @@ public function addImage( throw new LocalizedException(__("The image doesn't exist.")); } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $pathinfo = pathinfo($file); $imgExtensions = ['jpg', 'jpeg', 'gif', 'png']; if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) { @@ -166,10 +167,10 @@ public function addImage( } $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($pathinfo['basename']); - $dispretionPath = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName); - $fileName = $dispretionPath . '/' . $fileName; + $dispersionPath = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName); + $fileName = $dispersionPath . '/' . $fileName; - $fileName = $this->getNotDuplicatedFilename($fileName, $dispretionPath); + $fileName = $this->getNotDuplicatedFilename($fileName, $dispersionPath); $destinationFile = $this->mediaConfig->getTmpMediaPath($fileName); @@ -196,7 +197,7 @@ public function addImage( $mediaGalleryData = $product->getData($attrCode); $position = 0; - $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($file); + $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($destinationFile); $imageMimeType = $this->mime->getMimeType($absoluteFilePath); $imageContent = $this->mediaDirectory->readFile($absoluteFilePath); $imageBase64 = base64_encode($imageContent); @@ -452,6 +453,7 @@ protected function getUniqueFileName($file, $forTmp = false) $destinationFile = $forTmp ? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file)) : $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file)); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $destFile = dirname($file) . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName($destinationFile); } @@ -463,27 +465,27 @@ protected function getUniqueFileName($file, $forTmp = false) * Get filename which is not duplicated with other files in media temporary and media directories * * @param string $fileName - * @param string $dispretionPath + * @param string $dispersionPath * @return string * @since 101.0.0 */ - protected function getNotDuplicatedFilename($fileName, $dispretionPath) + protected function getNotDuplicatedFilename($fileName, $dispersionPath) { - $fileMediaName = $dispretionPath . '/' + $fileMediaName = $dispersionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName($this->mediaConfig->getMediaPath($fileName)); - $fileTmpMediaName = $dispretionPath . '/' + $fileTmpMediaName = $dispersionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName($this->mediaConfig->getTmpMediaPath($fileName)); if ($fileMediaName != $fileTmpMediaName) { if ($fileMediaName != $fileName) { return $this->getNotDuplicatedFilename( $fileMediaName, - $dispretionPath + $dispersionPath ); } elseif ($fileTmpMediaName != $fileName) { return $this->getNotDuplicatedFilename( $fileTmpMediaName, - $dispretionPath + $dispersionPath ); } } @@ -494,7 +496,7 @@ protected function getNotDuplicatedFilename($fileName, $dispretionPath) /** * Retrieve data for update attribute * - * @param \Magento\Catalog\Model\Product $object + * @param \Magento\Catalog\Model\Product $object * @return array * @since 101.0.0 */ diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php index d88dd58362896..31e178f0bd9b4 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php @@ -10,6 +10,8 @@ /** * Catalog product option select type + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Select extends \Magento\Catalog\Model\Product\Option\Type\DefaultType { @@ -30,23 +32,35 @@ class Select extends \Magento\Catalog\Model\Product\Option\Type\DefaultType */ protected $string; + /** + * @var array + */ + private $singleSelectionTypes; + /** * @param \Magento\Checkout\Model\Session $checkoutSession * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\Escaper $escaper * @param array $data + * @param array $singleSelectionTypes */ public function __construct( \Magento\Checkout\Model\Session $checkoutSession, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Stdlib\StringUtils $string, \Magento\Framework\Escaper $escaper, - array $data = [] + array $data = [], + array $singleSelectionTypes = [] ) { $this->string = $string; $this->_escaper = $escaper; parent::__construct($checkoutSession, $scopeConfig, $data); + + $this->singleSelectionTypes = $singleSelectionTypes ?: [ + 'drop_down' => \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + 'radio' => \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO, + ]; } /** @@ -310,10 +324,6 @@ public function getOptionSku($optionValue, $skuDelimiter) */ protected function _isSingleSelection() { - $single = [ - \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, - \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO, - ]; - return in_array($this->getOption()->getType(), $single); + return in_array($this->getOption()->getType(), $this->singleSelectionTypes, true); } } diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 6b4b229e2c125..f6f0800962756 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product; @@ -503,10 +505,10 @@ public function getFormattedTierPrice($qty, $product) /** * Get formatted by currency tier price * - * @param float $qty - * @param Product $product + * @param float $qty + * @param Product $product * - * @return array|float + * @return array|float * * @deprecated 102.0.6 * @see getFormattedTierPrice() @@ -531,8 +533,8 @@ public function getFormattedPrice($product) /** * Get formatted by currency product price * - * @param Product $product - * @return array || float + * @param Product $product + * @return array || float * * @deprecated 102.0.6 * @see getFormattedPrice() diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index f3ac9f55d1aea..2760b0f9fddb6 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -162,11 +162,8 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = []) \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); - if ($categoryId) { - $filterData[UrlRewrite::METADATA]['category_id'] = $categoryId; - } elseif (!$useCategories) { - $filterData[UrlRewrite::METADATA]['category_id'] = ''; - } + $filterData[UrlRewrite::METADATA]['category_id'] + = $categoryId && $useCategories ? $categoryId : ''; $rewrite = $this->urlFinder->findOneByData($filterData); diff --git a/app/code/Magento/Catalog/Model/Product/Visibility.php b/app/code/Magento/Catalog/Model/Product/Visibility.php index c863526898a24..c05bda7838d78 100644 --- a/app/code/Magento/Catalog/Model/Product/Visibility.php +++ b/app/code/Magento/Catalog/Model/Product/Visibility.php @@ -55,7 +55,7 @@ public function __construct( /** * Retrieve visible in catalog ids array * - * @return string[] + * @return int[] */ public function getVisibleInCatalogIds() { @@ -65,7 +65,7 @@ public function getVisibleInCatalogIds() /** * Retrieve visible in search ids array * - * @return string[] + * @return int[] */ public function getVisibleInSearchIds() { @@ -75,7 +75,7 @@ public function getVisibleInSearchIds() /** * Retrieve visible in site ids array * - * @return string[] + * @return int[] */ public function getVisibleInSiteIds() { @@ -86,6 +86,7 @@ public function getVisibleInSiteIds() * Retrieve option array * * @return array + * phpcs:disable Magento2.Functions.StaticFunction */ public static function getOptionArray() { @@ -134,6 +135,7 @@ public static function getOptionText($optionId) $options = self::getOptionArray(); return isset($options[$optionId]) ? $options[$optionId] : null; } + //phpcs:enable Magento2.Functions.StaticFunction /** * Retrieve flat column definition @@ -251,7 +253,7 @@ public function addValueSortToCollection($collection, $dir = 'asc') } /** - * {@inheritdoc} + * @inheritdoc */ public function toOptionArray() { diff --git a/app/code/Magento/Catalog/Model/ProductLink/Search.php b/app/code/Magento/Catalog/Model/ProductLink/Search.php index 8750345aa222b..ad7f3370ab3fe 100644 --- a/app/code/Magento/Catalog/Model/ProductLink/Search.php +++ b/app/code/Magento/Catalog/Model/ProductLink/Search.php @@ -10,7 +10,9 @@ use Magento\Catalog\Api\Data\ProductInterface; -/** Returns collection of product visible in catalog by search key */ +/** + * Returns collection of product visible in catalog by search key + */ class Search { /** @@ -58,7 +60,6 @@ public function prepareCollection( ): \Magento\Catalog\Model\ResourceModel\Product\Collection { $productCollection = $this->productCollectionFactory->create(); $productCollection->addAttributeToSelect(ProductInterface::NAME); - $productCollection->setVisibility($this->catalogVisibility->getVisibleInCatalogIds()); $productCollection->setPage($pageNum, $limit); $this->filter->addFilter($productCollection, 'fulltext', ['fulltext' => $searchKey]); $productCollection->setPage($pageNum, $limit); diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index edd8b5d50b851..c26a5d1bc56e0 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -369,8 +369,11 @@ protected function initializeProductData(array $productData, $createNew) if ($createNew) { $product = $this->productFactory->create(); $this->assignProductToWebsites($product); + } elseif (!empty($productData['id'])) { + $this->removeProductFromLocalCacheById($productData['id']); + $product = $this->getById($productData['id']); } else { - $this->removeProductFromLocalCache($productData['sku']); + $this->removeProductFromLocalCacheBySku($productData['sku']); $product = $this->get($productData['sku']); } @@ -512,7 +515,7 @@ public function save(ProductInterface $product, $saveOptions = false) $tierPrices = $product->getData('tier_price'); try { - $existingProduct = $this->get($product->getSku()); + $existingProduct = $product->getId() ? $this->getById($product->getId()) : $this->get($product->getSku()); $product->setData( $this->resourceModel->getLinkField(), @@ -570,8 +573,8 @@ public function save(ProductInterface $product, $saveOptions = false) $product->getCategoryIds() ); } - $this->removeProductFromLocalCache($product->getSku()); - unset($this->instancesById[$product->getId()]); + $this->removeProductFromLocalCacheBySku($product->getSku()); + $this->removeProductFromLocalCacheById($product->getId()); return $this->get($product->getSku(), false, $product->getStoreId()); } @@ -584,8 +587,8 @@ public function delete(ProductInterface $product) $sku = $product->getSku(); $productId = $product->getId(); try { - $this->removeProductFromLocalCache($product->getSku()); - unset($this->instancesById[$product->getId()]); + $this->removeProductFromLocalCacheBySku($product->getSku()); + $this->removeProductFromLocalCacheById($product->getId()); $this->resourceModel->delete($product); } catch (ValidatorException $e) { throw new CouldNotSaveException(__($e->getMessage()), $e); @@ -595,8 +598,8 @@ public function delete(ProductInterface $product) $e ); } - $this->removeProductFromLocalCache($sku); - unset($this->instancesById[$productId]); + $this->removeProductFromLocalCacheBySku($sku); + $this->removeProductFromLocalCacheById($productId); return true; } @@ -753,25 +756,36 @@ private function getProductFromLocalCache(string $sku, string $cacheKey) } /** - * Removes product in the local cache. + * Removes product in the local cache by sku. * * @param string $sku * @return void */ - private function removeProductFromLocalCache(string $sku) :void + private function removeProductFromLocalCacheBySku(string $sku): void { $preparedSku = $this->prepareSku($sku); unset($this->instances[$preparedSku]); } /** - * Saves product in the local cache. + * Removes product in the local cache by id. + * + * @param string|null $id + * @return void + */ + private function removeProductFromLocalCacheById(?string $id): void + { + unset($this->instancesById[$id]); + } + + /** + * Saves product in the local cache by sku. * * @param Product $product * @param string $cacheKey * @return void */ - private function saveProductInLocalCache(Product $product, string $cacheKey) : void + private function saveProductInLocalCache(Product $product, string $cacheKey): void { $preparedSku = $this->prepareSku($product->getSku()); $this->instances[$preparedSku][$cacheKey] = $product; @@ -800,8 +814,8 @@ private function prepareSku(string $sku): string private function saveProduct($product): void { try { - $this->removeProductFromLocalCache($product->getSku()); - unset($this->instancesById[$product->getId()]); + $this->removeProductFromLocalCacheBySku($product->getSku()); + $this->removeProductFromLocalCacheById($product->getId()); $this->resourceModel->save($product); } catch (ConnectionException $exception) { throw new TemporaryCouldNotSaveException( diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php index 70311954f63e9..fdcf2956dbdef 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -231,7 +231,7 @@ private function processEntries(ProductInterface $product, array $newEntries, ar private function processMediaAttributes(ProductInterface $product, array $images): void { foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { + if (empty($image['removed']) && !empty($image['types'])) { $this->processor->setMediaAttribute($product, $image['types'], $image['file']); } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php index e8c4f78264ede..8bd5c5cc37511 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php @@ -10,6 +10,8 @@ use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; use Magento\Catalog\Model\Product as ProductEntity; use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\Model\AbstractModel; /** * Product entity resource model @@ -45,7 +47,7 @@ class Product extends AbstractResource /** * Category collection factory * - * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory + * @var Category\CollectionFactory */ protected $_categoryCollectionFactory; @@ -65,7 +67,7 @@ class Product extends AbstractResource protected $typeFactory; /** - * @var \Magento\Framework\EntityManager\EntityManager + * @var EntityManager * @since 101.0.0 */ protected $entityManager; @@ -82,7 +84,7 @@ class Product extends AbstractResource protected $availableCategoryIdsCache = []; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\CategoryLink + * @var Product\CategoryLink */ private $productCategoryLink; @@ -111,7 +113,7 @@ public function __construct( \Magento\Eav\Model\Entity\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Factory $modelFactory, - \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory, + Category\CollectionFactory $categoryCollectionFactory, Category $catalogCategory, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Eav\Model\Entity\Attribute\SetFactory $setFactory, @@ -237,7 +239,7 @@ public function getWebsiteIdsByProductIds($productIds) /** * Retrieve product category identifiers * - * @param \Magento\Catalog\Model\Product $product + * @param \Magento\Catalog\Model\Product $product * @return array */ public function getCategoryIds($product) @@ -249,7 +251,7 @@ public function getCategoryIds($product) /** * Get product identifier by sku * - * @param string $sku + * @param string $sku * @return int|false */ public function getIdBySku($sku) @@ -349,11 +351,11 @@ protected function _saveCategories(\Magento\Framework\DataObject $object) * Get collection of product categories * * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Catalog\Model\ResourceModel\Category\Collection + * @return Category\Collection */ public function getCategoryCollection($product) { - /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */ + /** @var Category\Collection $collection */ $collection = $this->_categoryCollectionFactory->create(); $collection->joinField( 'product_id', @@ -429,18 +431,26 @@ public function getDefaultAttributeSourceModel() /** * Check availability display product in category * - * @param \Magento\Catalog\Model\Product $product + * @param \Magento\Catalog\Model\Product|int $product * @param int $categoryId * @return string */ public function canBeShowInCategory($product, $categoryId) { + if ($product instanceof \Magento\Catalog\Model\Product) { + $productId = $product->getEntityId(); + $storeId = $product->getStoreId(); + } else { + $productId = $product; + $storeId = $this->_storeManager->getStore()->getId(); + } + $select = $this->getConnection()->select()->from( - $this->tableMaintainer->getMainTable($product->getStoreId()), + $this->tableMaintainer->getMainTable($storeId), 'product_id' )->where( 'product_id = ?', - (int)$product->getEntityId() + (int)$productId )->where( 'category_id = ?', (int)$categoryId @@ -617,7 +627,7 @@ public function validate($object) /** * Reset firstly loaded attributes * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @param integer $entityId * @param array|null $attributes * @return $this @@ -670,12 +680,12 @@ protected function evaluateDelete($object, $id, $connection) /** * Save entity's attributes into the object's resource * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return $this * @throws \Exception * @since 101.0.0 */ - public function save(\Magento\Framework\Model\AbstractModel $object) + public function save(AbstractModel $object) { $this->getEntityManager()->save($object); return $this; @@ -684,13 +694,13 @@ public function save(\Magento\Framework\Model\AbstractModel $object) /** * Retrieve entity manager. * - * @return \Magento\Framework\EntityManager\EntityManager + * @return EntityManager */ private function getEntityManager() { if (null === $this->entityManager) { - $this->entityManager = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\EntityManager::class); + $this->entityManager = ObjectManager::getInstance() + ->get(EntityManager::class); } return $this->entityManager; } @@ -710,13 +720,13 @@ private function getProductWebsiteLink() * Retrieve CategoryLink instance. * * @deprecated 102.0.0 - * @return \Magento\Catalog\Model\ResourceModel\Product\CategoryLink + * @return Product\CategoryLink */ private function getProductCategoryLink() { if (null === $this->productCategoryLink) { - $this->productCategoryLink = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\ResourceModel\Product\CategoryLink::class); + $this->productCategoryLink = ObjectManager::getInstance() + ->get(Product\CategoryLink::class); } return $this->productCategoryLink; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index c1bdc7863ab55..6dc20ab12d606 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -13,13 +13,13 @@ use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\Storage\DbStorage; use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; @@ -297,6 +297,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $emptyItem; + /** + * @var DbStorage + */ + private $urlFinder; + /** * Collection constructor * @@ -389,6 +394,19 @@ public function __construct( ?: ObjectManager::getInstance()->get(DimensionFactory::class); } + /** + * Retrieve urlFinder + * + * @return GalleryReadHandler + */ + private function getUrlFinder() + { + if ($this->urlFinder === null) { + $this->urlFinder = ObjectManager::getInstance()->get(DbStorage::class); + } + return $this->urlFinder; + } + /** * Get cloned Select after dispatching 'catalog_prepare_price_select' event * @@ -809,7 +827,7 @@ public function load($printQuery = false, $logQuery = false) } /** - * Processs adding product website names to result collection + * Process adding product website names to result collection * * @return $this */ @@ -1419,44 +1437,21 @@ protected function _addUrlRewrite() foreach ($this->getItems() as $item) { $productIds[] = $item->getEntityId(); } - if (!$productIds) { - return; - } - - $select = $this->getConnection() - ->select() - ->from(['u' => $this->getTable('url_rewrite')], ['u.entity_id', 'u.request_path']) - ->where('u.store_id = ?', $this->_storeManager->getStore($this->getStoreId())->getId()) - ->where('u.is_autogenerated = 1') - ->where('u.entity_type = ?', ProductUrlRewriteGenerator::ENTITY_TYPE) - ->where('u.entity_id IN(?)', $productIds); + $filter = [ + 'entity_type' => 'product', + 'entity_id' => $productIds, + 'store_id' => $this->getStoreId(), + 'is_autogenerated' => 1 + ]; if ($this->_urlRewriteCategory) { - $select->joinInner( - ['cu' => $this->getTable('catalog_url_rewrite_product_category')], - 'u.url_rewrite_id=cu.url_rewrite_id' - )->where('cu.category_id IN (?)', $this->_urlRewriteCategory); - } else { - $select->joinLeft( - ['cu' => $this->getTable('catalog_url_rewrite_product_category')], - 'u.url_rewrite_id=cu.url_rewrite_id' - )->where('cu.url_rewrite_id IS NULL'); - } - - // more priority is data with category id - $urlRewrites = []; - - foreach ($this->getConnection()->fetchAll($select) as $row) { - if (!isset($urlRewrites[$row['entity_id']])) { - $urlRewrites[$row['entity_id']] = $row['request_path']; - } + $filter['metadata']['category_id'] = $this->_urlRewriteCategory; } - foreach ($this->getItems() as $item) { - if (isset($urlRewrites[$item->getEntityId()])) { - $item->setData('request_path', $urlRewrites[$item->getEntityId()]); - } else { - $item->setData('request_path', false); + $rewrites = $this->getUrlFinder()->findAllByData($filter); + foreach ($rewrites as $rewrite) { + if ($item = $this->getItemById($rewrite->getEntityId())) { + $item->setData('request_path', $rewrite->getRequestPath()); } } } @@ -1584,26 +1579,9 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType = $this->_allIdsCache = null; if (is_string($attribute) && $attribute == 'is_saleable') { - $columns = $this->getSelect()->getPart(\Magento\Framework\DB\Select::COLUMNS); - foreach ($columns as $columnEntry) { - list($correlationName, $column, $alias) = $columnEntry; - if ($alias == 'is_saleable') { - if ($column instanceof \Zend_Db_Expr) { - $field = $column; - } else { - $connection = $this->getSelect()->getConnection(); - if (empty($correlationName)) { - $field = $connection->quoteColumnAs($column, $alias, true); - } else { - $field = $connection->quoteColumnAs([$correlationName, $column], $alias, true); - } - } - $this->getSelect()->where("{$field} = ?", $condition); - break; - } - } - - return $this; + $this->addIsSaleableAttributeToFilter($condition); + } elseif (is_string($attribute) && $attribute == 'tier_price') { + $this->addTierPriceAttributeToFilter($attribute, $condition); } else { return parent::addAttributeToFilter($attribute, $condition, $joinType); } @@ -1978,8 +1956,7 @@ protected function _productLimitationPrice($joinLeft = false) } // Set additional field filters foreach ($this->_priceDataFieldFilters as $filterData) { - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $select->where(call_user_func_array('sprintf', $filterData)); + $select->where(sprintf(...$filterData)); } } else { $fromPart['price_index']['joinCondition'] = $joinCond; @@ -2284,8 +2261,7 @@ private function getBackend() public function addPriceDataFieldFilter($comparisonFormat, $fields) { if (!preg_match('/^%s( (<|>|=|<=|>=|<>) %s)*$/', $comparisonFormat)) { - // phpcs:ignore Magento2.Exceptions.DirectThrow - throw new \Exception('Invalid comparison format.'); + throw new \InvalidArgumentException('Invalid comparison format.'); } if (!is_array($fields)) { @@ -2489,4 +2465,71 @@ public function getPricesCount() return $this->_pricesCount; } + + /** + * Add is_saleable attribute to filter + * + * @param array|null $condition + * @return $this + */ + private function addIsSaleableAttributeToFilter(?array $condition): self + { + $columns = $this->getSelect()->getPart(Select::COLUMNS); + foreach ($columns as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($alias == 'is_saleable') { + if ($column instanceof \Zend_Db_Expr) { + $field = $column; + } else { + $connection = $this->getSelect()->getConnection(); + if (empty($correlationName)) { + $field = $connection->quoteColumnAs($column, $alias, true); + } else { + $field = $connection->quoteColumnAs([$correlationName, $column], $alias, true); + } + } + $this->getSelect()->where("{$field} = ?", $condition); + break; + } + } + + return $this; + } + + /** + * Add tier price attribute to filter + * + * @param string $attribute + * @param array|null $condition + * @return $this + */ + private function addTierPriceAttributeToFilter(string $attribute, ?array $condition): self + { + $attrCode = $attribute; + $connection = $this->getConnection(); + $attrTable = $this->_getAttributeTableAlias($attrCode); + $entity = $this->getEntity(); + $fKey = 'e.' . $this->getEntityPkName($entity); + $pKey = $attrTable . '.' . $this->getEntityPkName($entity); + $attribute = $entity->getAttribute($attrCode); + $attrFieldName = $attrTable . '.value'; + $fKey = $connection->quoteColumnAs($fKey, null); + $pKey = $connection->quoteColumnAs($pKey, null); + + $condArr = ["{$pKey} = {$fKey}"]; + $this->getSelect()->join( + [$attrTable => $this->getTable('catalog_product_entity_tier_price')], + '(' . implode(') AND (', $condArr) . ')', + [$attrCode => $attrFieldName] + ); + $this->removeAttributeToSelect($attrCode); + $this->_filterAttributes[$attrCode] = $attribute->getId(); + $this->_joinFields[$attrCode] = ['table' => '', 'field' => $attrFieldName]; + $field = $this->_getAttributeTableAlias($attrCode) . '.value'; + $conditionSql = $this->_getConditionSql($field, $condition); + $this->getSelect()->where($conditionSql, null, Select::TYPE_CONDITION); + $this->_totalRecords = null; + + return $this; + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php index aa6fb8c1f8827..92741cf9ba88e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php @@ -76,6 +76,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem * @param \Magento\Catalog\Helper\Product\Compare $catalogProductCompare * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php index 77f67480619e0..0f324194b7f53 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php @@ -54,7 +54,7 @@ public function __construct( } /** - * Returns product images + * Get all product images. * * @return \Generator */ @@ -75,7 +75,28 @@ public function getAllProductImages(): \Generator } /** - * Get the number of unique pictures of products + * Get used product images. + * + * @return \Generator + */ + public function getUsedProductImages(): \Generator + { + $batchSelectIterator = $this->batchQueryGenerator->generate( + 'value_id', + $this->getUsedImagesSelect(), + $this->batchSize, + \Magento\Framework\DB\Query\BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR + ); + + foreach ($batchSelectIterator as $select) { + foreach ($this->connection->fetchAll($select) as $key => $value) { + yield $key => $value; + } + } + } + + /** + * Get the number of unique images of products. * * @return int */ @@ -92,7 +113,24 @@ public function getCountAllProductImages(): int } /** - * Return Select to fetch all products images + * Get the number of unique and used images of products. + * + * @return int + */ + public function getCountUsedProductImages(): int + { + $select = $this->getUsedImagesSelect() + ->reset('columns') + ->reset('distinct') + ->columns( + new \Zend_Db_Expr('count(distinct value)') + ); + + return (int) $this->connection->fetchOne($select); + } + + /** + * Return select to fetch all products images. * * @return Select */ @@ -106,4 +144,24 @@ private function getVisibleImagesSelect(): Select 'disabled = 0' ); } + + /** + * Return select to fetch all used product images. + * + * @return Select + */ + private function getUsedImagesSelect(): Select + { + return $this->connection->select()->distinct() + ->from( + ['images' => $this->resourceConnection->getTableName(Gallery::GALLERY_TABLE)], + 'value as filepath' + )->joinInner( + ['image_value' => $this->resourceConnection->getTableName(Gallery::GALLERY_VALUE_TABLE)], + 'images.value_id = image_value.value_id', + [] + )->where( + 'images.disabled = 0 AND image_value.disabled = 0' + ); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 83f7de6dc3d9e..747b06266cce0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -259,7 +259,8 @@ private function prepareFinalPriceTable() $tableName = $this->_getDefaultFinalPriceTable(); $this->getConnection()->delete($tableName); - $finalPriceTable = $this->indexTableStructureFactory->create([ + $finalPriceTable = $this->indexTableStructureFactory->create( + [ 'tableName' => $tableName, 'entityField' => 'entity_id', 'customerGroupField' => 'customer_group_id', @@ -270,7 +271,8 @@ private function prepareFinalPriceTable() 'minPriceField' => 'min_price', 'maxPriceField' => 'max_price', 'tierPriceField' => 'tier_price', - ]); + ] + ); return $finalPriceTable; } @@ -465,11 +467,13 @@ protected function getSelect($entityIds = null, $type = null) ); $tierPrice = $this->getTotalTierPriceExpression($price); $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint); - $finalPrice = $connection->getLeastSql([ + $finalPrice = $connection->getLeastSql( + [ $price, $specialPriceExpr, $tierPriceExpr, - ]); + ] + ); $select->columns( [ @@ -848,7 +852,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) ] ), 'NULL', - $this->getConnection()->getLeastSql([ + $this->getConnection()->getLeastSql( + [ $this->getConnection()->getIfNullSql( $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression), $maxUnsignedBigint @@ -865,7 +870,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression), $maxUnsignedBigint ), - ]) + ] + ) ); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php index 95fecc832fa26..77407ed699fbd 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php @@ -200,11 +200,13 @@ public function getQuery(array $dimensions, string $productType, array $entityId ); $tierPrice = $this->getTotalTierPriceExpression($price); $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint); - $finalPrice = $connection->getLeastSql([ + $finalPrice = $connection->getLeastSql( + [ $price, $specialPriceExpr, $tierPriceExpr, - ]); + ] + ); $select->columns( [ @@ -221,11 +223,8 @@ public function getQuery(array $dimensions, string $productType, array $entityId $select->where("e.type_id = ?", $productType); if ($entityIds !== null) { - if (count($entityIds) > 1) { - $select->where(sprintf('e.entity_id BETWEEN %s AND %s', min($entityIds), max($entityIds))); - } else { - $select->where('e.entity_id = ?', $entityIds); - } + $select->where(sprintf('e.entity_id BETWEEN %s AND %s', min($entityIds), max($entityIds))); + $select->where('e.entity_id IN(?)', $entityIds); } /** @@ -265,7 +264,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) ] ), 'NULL', - $this->getConnection()->getLeastSql([ + $this->getConnection()->getLeastSql( + [ $this->getConnection()->getIfNullSql( $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression), $maxUnsignedBigint @@ -282,7 +282,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression), $maxUnsignedBigint ), - ]) + ] + ) ); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php index 179da06b59990..2238ad91550e4 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php @@ -6,6 +6,10 @@ namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; /** * Catalog product custom option resource model @@ -76,10 +80,10 @@ protected function _construct() /** * Save options store data * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ - protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) + protected function _afterSave(AbstractModel $object) { $this->_saveValuePrices($object); $this->_saveValueTitles($object); @@ -90,136 +94,38 @@ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) /** * Save value prices * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _saveValuePrices(\Magento\Framework\Model\AbstractModel $object) + protected function _saveValuePrices(AbstractModel $object) { - $priceTable = $this->getTable('catalog_product_option_price'); - $connection = $this->getConnection(); - /* * Better to check param 'price' and 'price_type' for saving. * If there is not price skip saving price */ - if (in_array($object->getType(), $this->getPriceTypes())) { - //save for store_id = 0 + // save for store_id = 0 if (!$object->getData('scope', 'price')) { - $statement = $connection->select()->from( - $priceTable, - 'option_id' - )->where( - 'option_id = ?', - $object->getId() - )->where( - 'store_id = ?', - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ); - $optionId = $connection->fetchOne($statement); - - if ($optionId) { - $data = $this->_prepareDataForTable( - new \Magento\Framework\DataObject( - ['price' => $object->getPrice(), 'price_type' => $object->getPriceType()] - ), - $priceTable - ); - - $connection->update( - $priceTable, - $data, - [ - 'option_id = ?' => $object->getId(), - 'store_id = ?' => \Magento\Store\Model\Store::DEFAULT_STORE_ID - ] - ); - } else { - $data = $this->_prepareDataForTable( - new \Magento\Framework\DataObject( - [ - 'option_id' => $object->getId(), - 'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, - 'price' => $object->getPrice(), - 'price_type' => $object->getPriceType(), - ] - ), - $priceTable - ); - $connection->insert($priceTable, $data); - } + $this->savePriceByStore($object, Store::DEFAULT_STORE_ID); } $scope = (int)$this->_config->getValue( - \Magento\Store\Model\Store::XML_PATH_PRICE_SCOPE, + Store::XML_PATH_PRICE_SCOPE, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); - if ($object->getStoreId() != '0' && $scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) { - $baseCurrency = $this->_config->getValue( - \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, - 'default' - ); - + if ($object->getStoreId() != '0' && $scope == Store::PRICE_SCOPE_WEBSITE) { $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); - if (is_array($storeIds)) { - foreach ($storeIds as $storeId) { - if ($object->getPriceType() == 'fixed') { - $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); - $rate = $this->_currencyFactory->create()->load($baseCurrency)->getRate($storeCurrency); - if (!$rate) { - $rate = 1; - } - $newPrice = $object->getPrice() * $rate; - } else { - $newPrice = $object->getPrice(); - } - - $statement = $connection->select()->from( - $priceTable - )->where( - 'option_id = ?', - $object->getId() - )->where( - 'store_id = ?', - $storeId - ); - - if ($connection->fetchOne($statement)) { - $data = $this->_prepareDataForTable( - new \Magento\Framework\DataObject( - ['price' => $newPrice, 'price_type' => $object->getPriceType()] - ), - $priceTable - ); - - $connection->update( - $priceTable, - $data, - ['option_id = ?' => $object->getId(), 'store_id = ?' => $storeId] - ); - } else { - $data = $this->_prepareDataForTable( - new \Magento\Framework\DataObject( - [ - 'option_id' => $object->getId(), - 'store_id' => $storeId, - 'price' => $newPrice, - 'price_type' => $object->getPriceType(), - ] - ), - $priceTable - ); - $connection->insert($priceTable, $data); - } - } + if (empty($storeIds)) { + return $this; } - } elseif ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE && $object->getData('scope', 'price') - ) { - $connection->delete( - $priceTable, + foreach ($storeIds as $storeId) { + $newPrice = $this->calculateStorePrice($object, $storeId); + $this->savePriceByStore($object, (int)$storeId, $newPrice); + } + } elseif ($scope == Store::PRICE_SCOPE_WEBSITE && $object->getData('scope', 'price')) { + $this->getConnection()->delete( + $this->getTable('catalog_product_option_price'), ['option_id = ?' => $object->getId(), 'store_id = ?' => $object->getStoreId()] ); } @@ -228,31 +134,114 @@ protected function _saveValuePrices(\Magento\Framework\Model\AbstractModel $obje return $this; } + /** + * Save option price by store + * + * @param AbstractModel $object + * @param int $storeId + * @param float|null $newPrice + */ + private function savePriceByStore(AbstractModel $object, int $storeId, float $newPrice = null): void + { + $priceTable = $this->getTable('catalog_product_option_price'); + $connection = $this->getConnection(); + $price = $newPrice === null ? $object->getPrice() : $newPrice; + + $statement = $connection->select()->from($priceTable, 'option_id') + ->where('option_id = ?', $object->getId()) + ->where('store_id = ?', $storeId); + $optionId = $connection->fetchOne($statement); + + if (!$optionId) { + $data = $this->_prepareDataForTable( + new DataObject( + [ + 'option_id' => $object->getId(), + 'store_id' => $storeId, + 'price' => $price, + 'price_type' => $object->getPriceType(), + ] + ), + $priceTable + ); + $connection->insert($priceTable, $data); + } else { + // skip to update the default price when the store price is saving + if ($storeId === Store::DEFAULT_STORE_ID && (int)$object->getStoreId() !== $storeId) { + return; + } + + $data = $this->_prepareDataForTable( + new DataObject( + [ + 'price' => $price, + 'price_type' => $object->getPriceType() + ] + ), + $priceTable + ); + + $connection->update( + $priceTable, + $data, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => $storeId + ] + ); + } + } + + /** + * Calculate price by store + * + * @param AbstractModel $object + * @param int $storeId + * @return float + */ + private function calculateStorePrice(AbstractModel $object, int $storeId): float + { + $price = $object->getPrice(); + if ($object->getPriceType() == 'fixed') { + $website = $this->_storeManager->getStore($storeId)->getWebsite(); + $websiteBaseCurrency = $this->_config->getValue( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, + ScopeInterface::SCOPE_WEBSITE, + $website + ); + $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); + $rate = $this->_currencyFactory->create()->load($websiteBaseCurrency)->getRate($storeCurrency); + $price = $object->getPrice() * ($rate ?: 1); + } + + return (float)$price; + } + /** * Save titles * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $object) + protected function _saveValueTitles(AbstractModel $object) { $connection = $this->getConnection(); $titleTableName = $this->getTable('catalog_product_option_title'); - foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { + foreach ([Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); - $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInDefaultStore = (int)$storeId == Store::DEFAULT_STORE_ID ? $existInCurrentStore : $this->getColFromOptionTable( $titleTableName, (int)$object->getId(), - \Magento\Store\Model\Store::DEFAULT_STORE_ID + Store::DEFAULT_STORE_ID ); if ($object->getTitle()) { $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_title'); if ($existInCurrentStore) { - if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + if ($isDeleteStoreTitle && (int)$storeId != Store::DEFAULT_STORE_ID) { $connection->delete($titleTableName, ['option_title_id = ?' => $existInCurrentStore]); } elseif ($object->getStoreId() == $storeId) { $data = $this->_prepareDataForTable( @@ -270,9 +259,9 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje } } else { // we should insert record into not default store only of if it does not exist in default store - if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + if (($storeId == Store::DEFAULT_STORE_ID && !$existInDefaultStore) || ( - $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + $storeId != Store::DEFAULT_STORE_ID && !$existInCurrentStore && !$isDeleteStoreTitle ) @@ -291,7 +280,7 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje } } } else { - if ($object->getId() && $object->getStoreId() > \Magento\Store\Model\Store::DEFAULT_STORE_ID + if ($object->getId() && $object->getStoreId() > Store::DEFAULT_STORE_ID && $storeId ) { $connection->delete( @@ -470,7 +459,7 @@ public function getSearchableData($productId, $storeId) 'option_title_default.option_id=product_option.option_id', $connection->quoteInto( 'option_title_default.store_id = ?', - \Magento\Store\Model\Store::DEFAULT_STORE_ID + Store::DEFAULT_STORE_ID ) ] ); @@ -517,7 +506,7 @@ public function getSearchableData($productId, $storeId) 'option_title_default.option_type_id=option_type.option_type_id', $connection->quoteInto( 'option_title_default.store_id = ?', - \Magento\Store\Model\Store::DEFAULT_STORE_ID + Store::DEFAULT_STORE_ID ) ] ); @@ -582,6 +571,8 @@ public function getPriceTypes() } /** + * Get Metadata Pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php index ed9f89efc6891..a898075befd16 100644 --- a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php +++ b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php @@ -38,10 +38,9 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var $product \Magento\Catalog\Model\Product */ $product = $observer->getEvent()->getProduct(); - if ($product->getSpecialPrice() && ! $product->getSpecialFromDate()) { + if ($product->getSpecialPrice() && $product->getSpecialFromDate() === null) { $product->setData('special_from_date', $this->localeDate->date()->setTime(0, 0)); } - return $this; } } diff --git a/app/code/Magento/Catalog/Pricing/Price/Collection.php b/app/code/Magento/Catalog/Pricing/Price/Collection.php new file mode 100644 index 0000000000000..b48d7e9e38361 --- /dev/null +++ b/app/code/Magento/Catalog/Pricing/Price/Collection.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Catalog\Pricing\Price; + +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Pricing\SaleableInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Pricing\Price\Factory; +use Magento\Framework\Pricing\Price\Pool; + +/** + * Price models collection class. + */ +class Collection extends \Magento\Framework\Pricing\Price\Collection +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param SaleableInterface $saleableItem + * @param Factory $priceFactory + * @param Pool $pool + * @param float $quantity + * @param StoreManagerInterface|null $storeManager + */ + public function __construct( + SaleableInterface $saleableItem, + Factory $priceFactory, + Pool $pool, + $quantity, + StoreManagerInterface $storeManager = null + ) { + parent::__construct($saleableItem, $priceFactory, $pool, $quantity); + $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class); + } + + /** + * @inheritdoc + */ + public function get($code) + { + $customerGroupId = $this->saleableItem->getCustomerGroupId() ?? ''; + $websiteId = $this->storeManager->getStore($this->saleableItem->getStoreId())->getWebsiteId(); + $codeKey = $code . '-' . $customerGroupId . '-' . $websiteId; + + if (!isset($this->priceModels[$codeKey])) { + $this->priceModels[$codeKey] = $this->priceFactory->create( + $this->saleableItem, + $this->pool[$code], + $this->quantity + ); + } + + return $this->priceModels[$codeKey]; + } +} diff --git a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php index e0a92ea0e0bea..71b5f505f97a8 100644 --- a/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php @@ -60,7 +60,7 @@ public function __construct( } /** - * @return string + * @inheritdoc */ protected function _toHtml() { @@ -182,25 +182,27 @@ public function showMinimalPrice() */ public function getCacheKey() { - return parent::getCacheKey() . ($this->getData('list_category_page') ? '-list-category-page': ''); + return parent::getCacheKey() + . ($this->getData('list_category_page') ? '-list-category-page': '') + . ($this->getSaleableItem()->getCustomerGroupId() ?? ''); } /** - * {@inheritdoc} - * - * @return array + * @inheritdoc */ public function getCacheKeyInfo() { $cacheKeys = parent::getCacheKeyInfo(); $cacheKeys['display_minimal_price'] = $this->getDisplayMinimalPrice(); $cacheKeys['is_product_list'] = $this->isProductList(); + $cacheKeys['customer_group_id'] = $this->getSaleableItem()->getCustomerGroupId(); return $cacheKeys; } /** - * Get flag that price rendering should be done for the list of products - * By default (if flag is not set) is false + * Get flag that price rendering should be done for the list of products. + * + * By default (if flag is not set) is false. * * @return bool */ diff --git a/app/code/Magento/Catalog/Pricing/Render/PriceBox.php b/app/code/Magento/Catalog/Pricing/Render/PriceBox.php index 190168ed583fc..678b45ce97e7b 100644 --- a/app/code/Magento/Catalog/Pricing/Render/PriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/PriceBox.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Pricing\Render; use Magento\Catalog\Model\Product; @@ -71,7 +73,9 @@ public function jsonEncode($valueToEncode) * * @param int $length * @param string|null $chars + * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function getRandomString($length, $chars = null) { @@ -93,4 +97,21 @@ public function getCanDisplayQty(Product $product) } return true; } + + /** + * Format percent + * + * @param float $percent + * + * @return string + */ + public function formatPercent(float $percent): string + { + /*First rtrim - trim zeros. So, 10.00 -> 10.*/ + /*Second rtrim - trim dot. So, 10. -> 10*/ + return rtrim( + rtrim(number_format($percent, 2), '0'), + '.' + ); + } } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml index a544be434f9c5..b47e13a118272 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml @@ -12,6 +12,7 @@ <argument name="product" defaultValue="product"/> </arguments> <amOnPage url="{{StorefrontProductPage.url(product.custom_attributes[url_key])}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdding"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdded"/> @@ -20,4 +21,10 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" time="30" stepKey="waitForProductAddedMessage"/> <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{product.name}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> </actionGroup> + <actionGroup name="StorefrontAddSimpleProductWithQtyActionGroup" extends="AddSimpleProductToCart"> + <arguments> + <argument name="quantity" type="string" defaultValue="1"/> + </arguments> + <fillField userInput="{{quantity}}" selector="{{StorefrontProductPageSection.qtyInput}}" stepKey="fillProductQty" after="goToProductPage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml new file mode 100644 index 0000000000000..9a4d15a0909fd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AddWebsiteToProductActionGroup"> + <arguments> + <argument name="website" type="string"/> + </arguments> + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToProductInWebsiteSectionHeader"/> + <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickProductInWebsiteSectionHeader"/> + <checkOption selector="{{ProductInWebsitesSection.website(website)}}" stepKey="checkWebsite"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForSimpleProductSaved"/> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertProductSaveSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml new file mode 100644 index 0000000000000..a8d2f7d9860f3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- You must already be on the product form > Advanced Inventory; + Action group can be used for customer group price and tier price --> + <actionGroup name="AdminAddAdvancedPricingToTheProductActionGroup"> + <arguments> + <argument name="index" type="string"/> + <argument name="groupPrice" type="entity"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.addCustomerGroupPrice}}" stepKey="clickCustomerGroupPriceAddButton"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect(index)}}" userInput="{{groupPrice.website_id}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect(index)}}" userInput="{{groupPrice.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput(index)}}" userInput="{{groupPrice.quantity}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput(index)}}" userInput="{{groupPrice.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + </actionGroup> + + <!-- Customer group is selected in different way for B2B --> + <actionGroup name="AdminAddAdvancedPricingToTheProductExtendedActionGroup" extends="AdminAddAdvancedPricingToTheProductActionGroup"> + <remove keyForRemoval="selectProductTierPriceCustomerGroupInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect(index)}}" stepKey="clickProductTierPriceCustGroupSelect" after="selectProductTierPriceWebsiteInput"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceGroupOrCatalogOption(groupPrice.customer_group)}}" time="30" stepKey="waitProductTierPriceGroupOrCatalogOption" after="clickProductTierPriceCustGroupSelect"/> + <click selector="{{AdminProductFormAdvancedPricingSection.productTierPriceGroupOrCatalogOption(groupPrice.customer_group)}}" stepKey="clickAllGroupsOption" after="waitProductTierPriceGroupOrCatalogOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml new file mode 100644 index 0000000000000..f0eef98748f8d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAddMinimumAdvertisedPriceActionGroup"> + <arguments> + <argument name="msrpData" type="entity"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitSpecialPrice"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.msrp}}" userInput="{{msrpData.msrp}}" stepKey="fillMinimumAdvertisedPrice"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.msrpType}}" userInput="{{msrpData.msrp_display_actual_price_type}}" stepKey="selectPriceType"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> + <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitForCloseModalWindow"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml new file mode 100644 index 0000000000000..8e50ac6edc68e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Add custom option, title and type --> + <actionGroup name="AdminAddProductCustomOptionActionGroup"> + <arguments> + <argument name="customOptionTitle" type="string"/> + <argument name="customOptionType" type="string"/> + </arguments> + <scrollTo selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="scrollToCustomizableOptionsSection"/> + <waitForPageLoad stepKey="waitForScrolling"/> + <click stepKey="clickAddOptions" selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}"/> + <waitForPageLoad stepKey="waitForAddProductPageLoad"/> + <fillField stepKey="fillInOptionTitle" selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{customOptionTitle}}"/> + <click stepKey="clickOptionTypeParent" selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}"/> + <waitForPageLoad stepKey="waitForDropdownOpen"/> + <click stepKey="clickOptionType" selector="{{AdminProductCustomizableOptionsSection.optionType(customOptionType)}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml new file mode 100644 index 0000000000000..3a03eb60555a3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Add value, value title and value price to custom options (type: drop-down, checkbox, multiple select, radio buttons) --> + <actionGroup name="AdminAddTitleAndPriceValueToCustomOptionActionGroup"> + <arguments> + <argument name="optionValue" type="entity"/> + </arguments> + <click stepKey="clickAddValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{optionValue.title}}"/> + <fillField stepKey="fillInValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{optionValue.price}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAnchorCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAnchorCategoryActionGroup.xml new file mode 100644 index 0000000000000..62e9a4b6615b2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAnchorCategoryActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAnchorCategoryActionGroup"> + <arguments> + <argument name="categoryName" type="string"/> + </arguments> + <!--Open Category page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryName)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Enable Anchor for category --> + <scrollTo selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" x="0" y="-80" stepKey="scrollToDisplaySetting"/> + <click selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" stepKey="selectDisplaySetting"/> + <checkOption selector="{{CategoryDisplaySettingsSection.anchor}}" stepKey="enableAnchor"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml new file mode 100644 index 0000000000000..1ed186f34146f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductsGridIsEmptyActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertProductsGridIsEmptyActionGroup"> + <see selector="{{AdminCategoryProductsGridSection.productsGridEmpty}}" userInput="We couldn't find any records" stepKey="assertGridEmpty"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignCategoryToProductAndSaveActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignCategoryToProductAndSaveActionGroup.xml new file mode 100644 index 0000000000000..0cb7c4885ac77 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignCategoryToProductAndSaveActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssignCategoryToProductAndSaveActionGroup"> + <arguments> + <argument name="categoryName" type="string"/> + </arguments> + <!-- on edit Product page catalog/product/edit/id/{{product_id}}/ --> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="openDropDown"/> + <checkOption selector="{{AdminProductFormSection.selectCategory(categoryName)}}" stepKey="selectCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickDone"/> + <waitForPageLoad stepKey="waitForApplyCategory"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSavingProduct"/> + <see userInput="You saved the product." selector="{{CatalogProductsSection.messageSuccessSavedProduct}}" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml new file mode 100644 index 0000000000000..ec0beac86554e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeProductSEOSettingsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminChangeProductSEOSettingsActionGroup"> + <arguments> + <argument name="productName" defaultValue="_defaultProduct.name"/> + </arguments> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickSearchEngineOptimizationTab"/> + <waitForPageLoad stepKey="waitForTabOpen"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{productName}}" stepKey="setUrlKeyInput"/> + <fillField selector="{{AdminProductSEOSection.metaTitleInput}}" userInput="{{productName}}" stepKey="setMetaTitleInput"/> + <fillField selector="{{AdminProductSEOSection.metaKeywordsInput}}" userInput="{{productName}}" stepKey="setMetaKeywordsInput"/> + <fillField selector="{{AdminProductSEOSection.metaDescriptionInput}}" userInput="{{productName}}" stepKey="setMetaDescriptionInput"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml new file mode 100644 index 0000000000000..440739875c0c1 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCheckProductsInGridActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCheckProductIsMissingInCategoryProductsGrid"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <dontSee selector="{{AdminCategoryProductsGridSection.nameColumn}}" userInput="{{productName}}" stepKey="dontSeeProduct"/> + </actionGroup> + <actionGroup name="AdminCheckProductPositionInCategoryProductsGrid"> + <arguments> + <argument name="position" type="string"/> + <argument name="productName" type="string"/> + </arguments> + <see selector="{{AdminCategoryProductsGridSection.rowProductName(position)}}" userInput="{{productName}}" stepKey="assertProductPosition"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml new file mode 100644 index 0000000000000..58164ce5f8989 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- ActionGroup click on Advanced Inventory in product form; + You must already be on the product form page --> + <actionGroup name="AdminClickOnAdvancedInventoryLinkActionGroup"> + <click selector="{{AdminProductFormSection.advancedInventoryLink}}" stepKey="clickOnAdvancedInventoryLink"/> + <waitForPageLoad stepKey="waitForAdvancedInventoryPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml new file mode 100644 index 0000000000000..a674647a5c85b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateCustomDropDownOptionsActionGroup"> + <!-- ActionGroup will add a single custom option to a product + You must already be on the product creation page --> + <arguments> + <argument name="customOptionName" type="string"/> + <argument name="firstOption" type="entity"/> + <argument name="secondOption" type="entity"/> + </arguments> + <click stepKey="clickAddOptions" selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}"/> + <waitForPageLoad stepKey="waitForAddProductPageLoad"/> + <!-- Fill in the option and select the type of drop down --> + <fillField stepKey="fillInOptionTitle" selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{customOptionName}}"/> + <click stepKey="clickOptionTypeParent" selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}"/> + <waitForPageLoad stepKey="waitForDropdownOpen"/> + <click stepKey="clickOptionType" selector="{{AdminProductCustomizableOptionsSection.optionType('Drop-down')}}"/> + <!-- Add option based on the parameter --> + <click stepKey="clickAddFirstValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInFirstOptionValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{firstOption.title}}"/> + <fillField stepKey="fillInFirstOptionValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{firstOption.price}}"/> + <click stepKey="clickAddSecondValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInSecondOptionValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{secondOption.title}}"/> + <fillField stepKey="fillInSecondOptionValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{secondOption.price}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml new file mode 100644 index 0000000000000..d0116467be5ea --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminFillAdvancedInventoryOutOfStockThresholdActionGroup"> + <arguments> + <argument name="qty" type="string"/> + </arguments> + <uncheckOption selector="{{AdminProductFormAdvancedInventorySection.minQtyConfigSetting}}" stepKey="uncheckMiniQtyCheckBox"/> + <fillField selector="{{AdminProductFormAdvancedInventorySection.outOfStockThreshold}}" userInput="{{qty}}" stepKey="fillMiniAllowedQty"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml new file mode 100644 index 0000000000000..1faa3f04d366e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminFillAdvancedInventoryQtyActionGroup"> + <arguments> + <argument name="qty" type="string"/> + </arguments> + <fillField selector="{{AdminProductFormAdvancedInventorySection.advancedInventoryQty}}" userInput="{{qty}}" stepKey="fillQty"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml new file mode 100644 index 0000000000000..ec73001976dc6 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductAttributePropertiesActionGroup.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillProductAttributePropertiesActionGroup"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="attributeType" type="string"/> + </arguments> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{attributeName}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{attributeType}}" stepKey="selectInputType"/> + </actionGroup> + + <!--You are on AdminProductEditPage--> + <!-- Switch scope for product attribute--> + <!-- !Note! Scope : 0 - Store View; 1 - Global; 2 - Website; --> + <actionGroup name="AdminSwitchScopeForProductAttributeActionGroup"> + <arguments> + <argument name="scope" type="string" defaultValue="1"/> + </arguments> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <waitForElementVisible selector="{{AttributePropertiesSection.Scope}}" stepKey="waitOpenAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectNecessaryScope"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetByNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetByNameActionGroup.xml new file mode 100644 index 0000000000000..1ac7ac5e54bf5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetByNameActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenAttributeSetByNameActionGroup"> + <arguments> + <argument name="attributeSetName" type="string" defaultValue="Default"/> + </arguments> + <click selector="{{AdminProductAttributeSetGridSection.AttributeSetName(attributeSetName)}}" stepKey="chooseAttributeSet"/> + <waitForPageLoad stepKey="waitForAttributeSetPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetGridPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetGridPageActionGroup.xml new file mode 100644 index 0000000000000..c6f0c3332b1d5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAttributeSetGridPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenAttributeSetGridPageActionGroup"> + <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSetPage"/> + <waitForPageLoad stepKey="waitForAttributeSetPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductAttributePageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductAttributePageActionGroup.xml new file mode 100644 index 0000000000000..b6be3fb172d33 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductAttributePageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenProductAttributePageActionGroup"> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToAttributePage"/> + <waitForPageLoad stepKey="waitForAttributePageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductIndexPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductIndexPageActionGroup.xml new file mode 100644 index 0000000000000..ca1303f180ca4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductIndexPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenProductIndexPageActionGroup"> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndexPage"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index 3c44a8f1898ad..f419e8dcdb196 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -33,6 +33,7 @@ <arguments> <argument name="product" defaultValue="_defaultProduct"/> </arguments> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{product.sku}}" stepKey="fillProductSku"/> <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{product.price}}" stepKey="fillProductPrice"/> @@ -248,6 +249,10 @@ <waitForPageLoad time="60" stepKey="WaitForProductSave1"/> <see userInput="You saved the product." stepKey="seeSaveConfirmation"/> </actionGroup> + <actionGroup name="ProductSetAdvancedTierFixedPricing" extends="ProductSetAdvancedPricing"> + <remove keyForRemoval="selectProductTierPricePriceInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{amount}}" stepKey="selectProductTierPricePriceInput" after="selectProductTierPriceValueType"/> + </actionGroup> <!--Assert text in Related, Up-Sell or Cross-Sell section in Admin Product page--> <actionGroup name="AssertTextInAdminProductRelatedUpSellCrossSellSection"> @@ -274,8 +279,9 @@ <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> <waitForPageLoad stepKey="waitForPageToLoad"/> - <checkOption selector="{{AdminProductModalSlideGridSection.productRowCheckboxBySku(sku)}}" stepKey="selectProduct"/> + <click selector="{{AdminProductModalSlideGridSection.productGridXRowYColumnButton('1', '1')}}" stepKey="selectProduct"/> <click selector="{{AdminAddRelatedProductsModalSection.AddSelectedProductsButton}}" stepKey="addRelatedProductSelected"/> + <waitForElementNotVisible selector="{{AdminAddRelatedProductsModalSection.AddSelectedProductsButton}}" stepKey="waitForElementNotVisible"/> </actionGroup> <!--Click AddCrossSellProducts and adds product by SKU--> @@ -304,9 +310,11 @@ </arguments> <waitForPageLoad stepKey="waitForPageLoad"/> <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <waitForPageLoad stepKey="waitForAdvancedPricingModal"/> <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitSpecialPrice"/> <fillField userInput="{{price}}" selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="fillSpecialPrice"/> <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> + <waitForPageLoad stepKey="waitForAdvancedPricingModalGone"/> <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitForCloseModalWindow"/> </actionGroup> @@ -384,6 +392,14 @@ <waitForPageLoad stepKey="waitForSave"/> </actionGroup> + <!-- Action group assign to one website and unassign from another --> + <actionGroup name="AdminProcessProductWebsitesActionGroup" extends="CreatedProductConnectToWebsite"> + <arguments> + <argument name="websiteToUnassign"/> + </arguments> + <uncheckOption selector="{{ProductInWebsitesSection.website(websiteToUnassign.name)}}" after="SelectWebsite" stepKey="uncheckWebsite"/> + </actionGroup> + <!--Check tier price with a discount percentage on product--> <actionGroup name="AssertDiscountsPercentageOfProducts"> <arguments> @@ -439,6 +455,15 @@ </arguments> <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[{{categoryName}}]" stepKey="searchAndSelectCategory"/> </actionGroup> + <!--Remove category from product in ProductFrom Page--> + <actionGroup name="removeCategoryFromProduct"> + <arguments> + <argument name="categoryName" type="string" defaultValue="{{_defaultCategory.name}}"/> + </arguments> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <click selector="{{AdminProductFormSection.unselectCategories(categoryName)}}" stepKey="unselectCategories"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategory"/> + </actionGroup> <actionGroup name="expandAdminProductSection"> <arguments> @@ -483,4 +508,81 @@ <click selector="{{AdminAddUpSellProductsModalSection.AddSelectedProductsButton}}" stepKey="addRelatedProductSelected"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> </actionGroup> + <actionGroup name="adminProductAdvancedPricingNewCustomerGroupPrice"> + <arguments> + <argument name="qty" type="string" defaultValue="2"/> + <argument name="priceType" type="string" defaultValue="Discount"/> + <argument name="discount" type="string" defaultValue="75"/> + <argument name="customerGroup" type="string" defaultValue="0"/> + </arguments> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroup"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput(customerGroup)}}" userInput="{{qty}}" stepKey="fillProductTierPriceQtyInput1"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceValueTypeSelect(customerGroup)}}" userInput="{{priceType}}" stepKey="selectProductTierPriceValueType1"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput(customerGroup)}}" userInput="{{discount}}" stepKey="selectProductTierPricePriceInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton1"/> + </actionGroup> + <actionGroup name="AdminSetProductDisabled"> + <conditionalClick selector="{{AdminProductFormSection.enableProductLabel}}" dependentSelector="{{AdminProductFormSection.productStatusValue('1')}}" visible="true" stepKey="disableProduct"/> + <seeElement selector="{{AdminProductFormSection.productStatusValue('2')}}" stepKey="assertThatProductSetToDisabled"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert checkbox available for website in Product In Websites --> + <actionGroup name="AssertWebsiteIsAvailableInProductWebsites"> + <arguments> + <argument name="website" type="string"/> + </arguments> + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToProductInWebsitesSection"/> + <conditionalClick selector="{{ProductInWebsitesSection.sectionHeader}}" dependentSelector="{{ProductInWebsitesSection.sectionHeaderOpened}}" visible="false" stepKey="expandProductWebsitesSection"/> + <seeElement selector="{{ProductInWebsitesSection.website(website)}}" stepKey="seeCheckboxForWebsite"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert checkbox not available for website in Product In Websites --> + <actionGroup name="AssertWebsiteIsNotAvailableInProductWebsites" extends="AssertWebsiteIsAvailableInProductWebsites"> + <remove keyForRemoval="seeCheckboxForWebsite"/> + <dontSeeElement selector="{{ProductInWebsitesSection.website(website)}}" after="expandProductWebsitesSection" stepKey="dontSeeCheckboxForWebsite"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert checkbox Is checked for website in Product In Websites --> + <actionGroup name="AssertProductIsAssignedToWebsite" extends="AssertWebsiteIsAvailableInProductWebsites"> + <remove keyForRemoval="seeCheckboxForWebsite"/> + <seeCheckboxIsChecked selector="{{ProductInWebsitesSection.website(website)}}" after="expandProductWebsitesSection" stepKey="seeCustomWebsiteIsChecked"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert checkbox is not checked for website in Product In Websites --> + <actionGroup name="AssertProductIsNotAssignedToWebsite" extends="AssertWebsiteIsAvailableInProductWebsites"> + <remove keyForRemoval="seeCheckboxForWebsite"/> + <dontSeeCheckboxIsChecked selector="{{ProductInWebsitesSection.website(website)}}" after="expandProductWebsitesSection" stepKey="seeCustomWebsiteIsNotChecked"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert Product Name in admin Product page --> + <actionGroup name="AssertProductNameInProductEditForm"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{productName}}" stepKey="seeProductNameOnEditProductPage"/> + </actionGroup> + + <!-- You are on product Edit Page --> + <!-- Assert Product Description in admin Product page --> + <actionGroup name="AssertProductDescriptionInProductEditForm"> + <arguments> + <argument name="productDescription" type="string"/> + </arguments> + <conditionalClick selector="{{AdminProductContentSection.sectionHeader}}" dependentSelector="{{AdminProductContentSection.sectionHeaderShow}}" visible="false" stepKey="expandContentSection"/> + <seeInField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="{{productDescription}}" stepKey="seeProductDescription"/> + </actionGroup> + + <!-- You are on StorefrontProductPage --> + <!-- Check active product image --> + <actionGroup name="StorefrontAssertActiveProductImage"> + <arguments> + <argument name="fileName" defaultValue="magento-logo" type="string"/> + </arguments> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(fileName)}}" stepKey="seeActiveImageDefault"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml index 86158aba68f82..ed0c4387cdedf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -43,6 +43,13 @@ <selectOption selector="{{AdminProductFormNewAttributeSection.attributeType}}" userInput="{{attributeType}}" stepKey="selectAttributeType"/> <click selector="{{AdminProductFormNewAttributeSection.saveAttribute}}" stepKey="saveAttribute"/> </actionGroup> + <actionGroup name="AdminCreateAttributeFromProductPageWithScope" extends="AdminCreateAttributeFromProductPage" insertAfter="selectAttributeType"> + <arguments> + <argument name="scope" type="string" defaultValue="Store View"/> + </arguments> + <conditionalClick selector="{{AdminProductFormNewAttributeAdvancedSection.sectionHeader}}" dependentSelector="{{AdminProductFormNewAttributeAdvancedSection.scope}}" visible="false" stepKey="openAttributeAdvancedSection"/> + <selectOption selector="{{AdminProductFormNewAttributeAdvancedSection.scope}}" userInput="{{scope}}" stepKey="selectScope"/> + </actionGroup> <actionGroup name="AdminCreateAttributeWithValueWithTwoStoreViesFromProductPage" extends="AdminCreateAttributeFromProductPage"> <remove keyForRemoval="saveAttribute"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml new file mode 100644 index 0000000000000..57b180ada1536 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Update Product Name and Description attribute --> + <actionGroup name="AdminUpdateProductNameAndDescriptionAttributes"> + <arguments> + <argument name="product"/> + </arguments> + <click selector="{{AdminProductGridSection.productGridCheckboxOnRow('1')}}" stepKey="clickCheckbox"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickOption"/> + <waitForPageLoad stepKey="waitForUploadPage"/> + <seeInCurrentUrl url="{{ProductAttributesEditPage.url}}" stepKey="seeAttributePageEditUrl"/> + <click selector="{{AdminUpdateAttributesSection.toggleName}}" stepKey="clickToChangeName"/> + <fillField selector="{{AdminUpdateAttributesSection.name}}" userInput="{{product.name}}" stepKey="fillFieldName"/> + <click selector="{{AdminUpdateAttributesSection.toggleDescription}}" stepKey="clickToChangeDescription"/> + <fillField selector="{{AdminUpdateAttributesSection.description}}" userInput="{{product.description}}" stepKey="fillFieldDescription"/> + <click selector="{{AdminUpdateAttributesSection.saveButton}}" stepKey="save"/> + <waitForElementVisible selector="{{AdminMessagesSection.successMessage}}" stepKey="waitVisibleSuccessMessage"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="Message is added to queue" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index ad32b8edbd243..3e967cb9c6901 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -256,7 +256,7 @@ <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> <click selector="{{AdminProductGridSection.bulkActionOption('Change status')}}" stepKey="clickChangeStatusAction"/> - <click selector="{{AdminProductGridSection.changeStatus('status')}}" stepKey="clickChangeStatusDisabled" parameterized="true"/> + <click selector="{{AdminProductGridSection.changeStatus('status')}}" stepKey="clickChangeStatusDisabled"/> <waitForPageLoad stepKey="waitForStatusToBeChanged"/> <see selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) have been updated." stepKey="seeSuccessMessage"/> <waitForLoadingMaskToDisappear stepKey="waitForMaskToDisappear"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml new file mode 100644 index 0000000000000..7998cdc93732e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSetProductDesignSettingsActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSetProductDesignSettingsActionGroup"> + <arguments> + <argument name="designSettings" defaultValue="simpleBlankDesign"/> + </arguments> + <click selector="{{ProductDesignSection.DesignTab}}" stepKey="clickDesignTab"/> + <waitForPageLoad stepKey="waitForTabOpen"/> + <selectOption selector="{{ProductDesignSection.LayoutDropdown}}" userInput="{{designSettings.page_layout}}" stepKey="setLayout"/> + <selectOption selector="{{ProductDesignSection.productOptionsContainer}}" userInput="{{designSettings.options_container}}" stepKey="setDisplayProductOptions"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml new file mode 100644 index 0000000000000..e27454fb60491 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Click done button; + You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminSubmitAdvancedInventoryFormActionGroup"> + <click stepKey="clickOnDoneButton" selector="{{AdminProductFormAdvancedInventorySection.doneButton}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml new file mode 100644 index 0000000000000..4d650a727acc9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSwitchProductGiftMessageStatusActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSwitchProductGiftMessageStatusActionGroup"> + <arguments> + <argument name="status" defaultValue="0"/> + </arguments> + <click selector="{{AdminProductGiftOptionsSection.giftOptions}}" stepKey="clickToExpandGiftOptionsTab"/> + <waitForPageLoad stepKey="waitForGiftOptionsOpen"/> + <uncheckOption selector="{{AdminProductGiftOptionsSection.useConfigSettingsMessage}}" stepKey="uncheckConfigSettingsMessage"/> + <click selector="{{AdminProductGiftOptionsSection.toggleProductGiftMessage}}" stepKey="clickToGiftMessageSwitcher"/> + <seeElement selector="{{AdminProductGiftOptionsSection.giftMessageStatus('status')}}" stepKey="assertGiftMessageStatus"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignCategoryOnProductAndSaveActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignCategoryOnProductAndSaveActionGroup.xml new file mode 100644 index 0000000000000..35816f3cd6750 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminUnassignCategoryOnProductAndSaveActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminUnassignCategoryOnProductAndSaveActionGroup"> + <arguments> + <argument name="categoryName" type="string"/> + </arguments> + <!-- on edit Product page catalog/product/edit/id/{{product_id}}/ --> + <click selector="{{AdminProductFormSection.unselectCategories(categoryName)}}" stepKey="clearCategory"/> + <waitForPageLoad stepKey="waitForDelete"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSavingProduct"/> + <see userInput="You saved the product." selector="{{CatalogProductsSection.messageSuccessSavedProduct}}" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..3d0d16d105025 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertDontSeeProductDetailsOnStorefrontActionGroup"> + <arguments> + <argument name="productNumber" type="string"/> + <argument name="productInfo" type="string"/> + </arguments> + <dontSee selector="{{StorefrontCategoryProductSection.ProductInfoByNumber(productNumber)}}" userInput="{{productInfo}}" stepKey="seeProductInfo"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml new file mode 100644 index 0000000000000..2ae224c71a9bd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertErrorMessageAfterDeletingWebsiteActionGroup"> + <arguments> + <argument name="errorMessage" type="string"/> + </arguments> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPageLoad"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonFromAdminCategoryModalSection"/> + <see selector="{{AdminCategoryMessagesSection.errorMessage}}" userInput="{{errorMessage}}" stepKey="seeAssertErrorMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..68c6e92b93fa0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertProductDetailsOnStorefrontActionGroup"> + <arguments> + <argument name="productNumber" type="string"/> + <argument name="productInfo" type="string"/> + </arguments> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber(productNumber)}}" userInput="{{productInfo}}" stepKey="seeProductInfo"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomProductAttributeActionGroup.xml new file mode 100644 index 0000000000000..e27efb41a7ef7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontCustomProductAttributeActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontCustomProductAttributeActionGroup"> + <arguments> + <argument name="attributeLabel" type="string"/> + <argument name="attributeValue" type="string"/> + </arguments> + <see userInput="{{attributeLabel}}" selector="{{StorefrontProductMoreInformationSection.customAttributeLabel(attributeLabel)}}" stepKey="seeAttributeLabel" /> + <see userInput="{{attributeValue}}" selector="{{StorefrontProductMoreInformationSection.customAttributeValue(attributeLabel)}}" stepKey="seeAttributeValue" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml new file mode 100644 index 0000000000000..6e90fe7324dc4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount" userInput="{{productPrice}}"/> + <see selector="{{StorefrontProductInfoMainSection.updatedPrice}}" stepKey="productFinalPriceAmount" userInput="{{productFinalPrice}}"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml new file mode 100644 index 0000000000000..26693771bd1fd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertSubTotalOnStorefrontMiniCartActionGroup"> + <arguments> + <argument name="subTotal" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForShowCartButtonVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <grabTextFrom selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="grabTextFromMiniCartSubtotalField"/> + <assertEquals message="Mini shopping cart should contain subtotal {{subTotal}}" stepKey="assertSubtotalFieldFromMiniShoppingCart1"> + <expectedResult type="string">{{subTotal}}</expectedResult> + <actualResult type="variable">grabTextFromMiniCartSubtotalField</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml index b914d5e20712d..7723f32d14093 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml @@ -47,26 +47,26 @@ <fillField selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{option.title}}" stepKey="fillTitle"/> <click selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}" stepKey="openTypeSelect"/> <click selector="{{AdminProductCustomizableOptionsSection.optionType('File')}}" stepKey="selectTypeFile"/> - <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPrice('0')}}" stepKey="waitForElements"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice('0')}}" userInput="{{option.price}}" stepKey="fillPrice"/> - <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensions('0')}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPriceByTitle(option.title)}}" stepKey="waitForElements"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPriceByTitle(option.title)}}" userInput="{{option.price}}" stepKey="fillPrice"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceTypeByTitle(option.title)}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensionByTitle(option.title)}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> </actionGroup> <actionGroup name="AddProductCustomOptionField"> <arguments> <argument name="option" defaultValue="ProductOptionField"/> - <argiment name="optionIndex" type="string"/> </arguments> + <scrollTo selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="scrollToAddButtonOption"/> <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" stepKey="waitForOption"/> <fillField selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{option.title}}" stepKey="fillTitle"/> <click selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}" stepKey="openTypeSelect"/> <click selector="{{AdminProductCustomizableOptionsSection.optionType('Field')}}" stepKey="selectTypeFile"/> - <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="waitForElements"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" userInput="{{option.price}}" stepKey="fillPrice"/> - <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType(optionIndex)}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku(optionIndex)}}" userInput="{{option.title}}" stepKey="fillSku"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPriceByTitle(option.title)}}" stepKey="waitForElements"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPriceByTitle(option.title)}}" userInput="{{option.price}}" stepKey="fillPrice"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceTypeByTitle(option.title)}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSkuByTitle(option.title)}}" userInput="{{option.title}}" stepKey="fillSku"/> </actionGroup> <actionGroup name="importProductCustomizableOptions"> <arguments> @@ -90,7 +90,7 @@ <actionGroup name="checkCustomizableOptionImport"> <arguments> <argument name="option" defaultValue="ProductOptionField"/> - <argiment name="optionIndex" type="string"/> + <argument name="optionIndex" type="string"/> </arguments> <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionTitleInput(optionIndex)}}" stepKey="grabOptionTitle"/> <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="grabOptionPrice"/> @@ -99,4 +99,45 @@ <assertEquals expected="{{option.price}}" expectedType="string" actual="$grabOptionPrice" stepKey="assertOptionPrice"/> <assertEquals expected="{{option.title}}" expectedType="string" actual="$grabOptionSku" stepKey="assertOptionSku"/> </actionGroup> + <!-- Assumes we are on product edit page --> + <actionGroup name="AdminDeleteAllProductCustomOptions"> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="expandContentTab"/> + <waitForPageLoad time="10" stepKey="waitCustomizableOptionsTabOpened"/> + <executeInSelenium function="function($webdriver) use ($I) { + $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('[data-index=\'options\'] [data-index=\'delete_button\']')); + while(!empty($buttons)) { + $button = reset($buttons); + $I->executeJS('arguments[0].scrollIntoView(false)', [$button]); + $button->click(); + $webdriver->wait()->until(\Facebook\WebDriver\WebDriverExpectedCondition::stalenessOf($button)); + $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('[data-index=\'options\'] [data-index=\'delete_button\']')); + } + }" stepKey="deleteCustomOptions"/> + <dontSeeElement selector="{{AdminProductCustomizableOptionsSection.customOptionButtonDelete}}" stepKey="assertNoCustomOptions"/> + </actionGroup> + <!-- Assumes we are on product edit page --> + <actionGroup name="AdminAssertProductHasNoCustomOptions"> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="expandContentTab"/> + <waitForPageLoad time="10" stepKey="waitCustomizableOptionsTabOpened"/> + <dontSeeElement selector="{{AdminProductCustomizableOptionsSection.customOption}}" stepKey="assertNoCustomOptions"/> + </actionGroup> + <!-- Assumes we are on product edit page --> + <actionGroup name="AdminAssertProductHasNoCustomOption" extends="AdminAssertProductCustomOptionVisible"> + <remove keyForRemoval="assertCustomOptionVisible"/> + <dontSeeElement selector="{{AdminProductCustomizableOptionsSection.fillOptionTitle(option.title)}}" after="waitCustomizableOptionsTabOpened" stepKey="assertNoCustomOption"/> + </actionGroup> + <!-- Assumes we are on product edit page --> + <actionGroup name="AdminAssertProductCustomOptionVisible"> + <arguments> + <argument name="option" defaultValue="ProductOptionField"/> + </arguments> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="expandContentTab"/> + <waitForPageLoad time="10" stepKey="waitCustomizableOptionsTabOpened"/> + <seeElement selector="{{AdminProductCustomizableOptionsSection.fillOptionTitle(option.title)}}" stepKey="assertCustomOptionVisible"/> + </actionGroup> + <!-- Assumes we are on product edit page --> + <actionGroup name="AdminDeleteProductCustomOption" extends="AdminAssertProductCustomOptionVisible"> + <remove keyForRemoval="assertCustomOptionVisible"/> + <click selector="{{AdminProductCustomizableOptionsSection.deleteCustomOptions(option.title)}}" after="waitCustomizableOptionsTabOpened" stepKey="clickDeleteCustomOption"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml new file mode 100644 index 0000000000000..d5d378ad11bd9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FilterProductInGridByStoreViewAndNameActionGroup"> + <arguments> + <argument name="storeView" type="string"/> + <argument name="productName" type="string"/> + </arguments> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.storeViewDropdown(storeView)}}" stepKey="clickStoreViewDropdown"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{productName}}" stepKey="fillProductNameInNameFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <see selector="{{AdminProductGridFilterSection.nthRow('1')}}" userInput="{{productName}}" stepKey="seeFirstRowToVerifyProductVisibleInGrid"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStoreFrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStoreFrontProductPageActionGroup.xml new file mode 100644 index 0000000000000..4bfd5673e4a8b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStoreFrontProductPageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="OpenStoreFrontProductPageActionGroup"> + <arguments> + <argument name="productUrlKey" type="string"/> + </arguments> + <amOnPage url="{{StorefrontProductPage.url(productUrlKey)}}" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStorefrontProductPageByProductNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStorefrontProductPageByProductNameActionGroup.xml new file mode 100644 index 0000000000000..c25cdc403f8ec --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenStorefrontProductPageByProductNameActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="OpenStorefrontProductPageByProductNameActionGroup"> + <arguments> + <argument name="productName" type="string" defaultValue="{{_defaultProduct.name}}"/> + </arguments> + <amOnPage url="{{productName}}.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml new file mode 100644 index 0000000000000..5432d547e8025 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddProductToCartWithQtyActionGroup"> + <arguments> + <argument name="productQty" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="{{productQty}}" stepKey="fillProduct1Quantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> + <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeSuccessSaveMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml index c7ae52d2b37c3..080b374c60b43 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml @@ -14,6 +14,7 @@ </arguments> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{productName}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml new file mode 100644 index 0000000000000..bc922a40b05b7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertCustomOptionByTitleActionGroup"> + <arguments> + <argument name="title" type="string"/> + </arguments> + <seeElement selector="{{StorefrontProductInfoMainSection.customOptionByTitle(title)}}" stepKey="seeCustomOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml new file mode 100644 index 0000000000000..a5f1b92862be3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertGiftMessageFieldsActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertGiftMessageFieldsActionGroup"> + <waitForElementVisible selector="{{StorefrontProductCartGiftOptionSection.giftOptions}}" stepKey="waitForCartGiftOptionVisible"/> + <click selector="{{StorefrontProductCartGiftOptionSection.giftOptions}}" stepKey="clickGiftOptionBtn"/> + <seeElement selector="{{StorefrontProductCartGiftOptionSection.fieldTo}}" stepKey="seeFieldTo"/> + <seeElement selector="{{StorefrontProductCartGiftOptionSection.fieldFrom}}" stepKey="seeFieldFrom"/> + <seeElement selector="{{StorefrontProductCartGiftOptionSection.message}}" stepKey="seeMessageArea"/> + <seeElement selector="{{StorefrontProductCartGiftOptionSection.update}}" stepKey="seeUpdateButton"/> + <seeElement selector="{{StorefrontProductCartGiftOptionSection.cancel}}" stepKey="seeCancelButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml new file mode 100644 index 0000000000000..1de5933c6969a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert 404 Page Not Found on product detail page --> + <actionGroup name="StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup"> + <arguments> + <argument name="product"/> + </arguments> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="assert404Page"/> + <dontSee selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{product.name}}" stepKey="dontSeeProductName"/> + <seeInCurrentUrl url="/{{product.custom_attributes[url_key]}}.html" stepKey="checkProductUrl"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml index 1bb7c179dfca8..95f894b1293a0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml @@ -19,4 +19,12 @@ <click selector="{{StorefrontProductMediaSection.closeFullscreenImage}}" stepKey="closeFullScreenImage" /> <waitForPageLoad stepKey="waitForGalleryDisappear" /> </actionGroup> + + <!--Check availability image in fotorama--> + <actionGroup name="StorefrontAssertFotoramaImageAvailablity"> + <arguments> + <argument name="fileName" type="string" defaultValue="magento-logo"/> + </arguments> + <seeElement selector="{{StorefrontProductMediaSection.productImageInFotorama(fileName)}}" stepKey="seeActiveImageDefault"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml new file mode 100644 index 0000000000000..43a34448c8a68 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductNameOnProductMainPageActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForTheProductPageToLoad"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{productName}}" stepKey="seeProductName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml new file mode 100644 index 0000000000000..9fefa71f10209 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSpecialPriceOnProductPageActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductSpecialPriceOnProductPageActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="specialPrice" type="string"/> + </arguments> + <amOnPage url="{{StorefrontProductPage.url(product.name)}}" stepKey="onFirstProductPage"/> + <waitForPageLoad stepKey="waitForFirstProductPage"/> + <waitForElementVisible selector="{{StorefrontProductInfoMainSection.specialPriceValue}}" stepKey="waitForProductSpecialPrice"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.specialPriceValue}}" stepKey="grabProductSpecialPrice"/> + <assertEquals actual="$grabProductSpecialPrice" expectedType="string" expected="{{specialPrice}}" stepKey="assertProductPriceValuesAreEqual"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 4c7c011028c92..7e79182616fd0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -62,6 +62,13 @@ <seeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="AssertAddToCart" /> </actionGroup> + <actionGroup name="StorefrontCheckAddToCartButtonAbsence"> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + </arguments> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(product.name)}}" stepKey="moveMouseOverProduct" /> + <dontSeeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="checkAddToCartButtonAbsence"/> + </actionGroup> <actionGroup name="StorefrontSwitchCategoryViewToListMode"> <click selector="{{StorefrontCategoryMainSection.modeListButton}}" stepKey="switchCategoryViewToListMode"/> <waitForElement selector="{{StorefrontCategoryMainSection.CategoryTitle}}" time="30" stepKey="waitForCategoryReload"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml new file mode 100644 index 0000000000000..01751a32d2e06 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert product is missing in category products page --> + <actionGroup name="StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <dontSee selector="{{StorefrontCategoryProductSection.ProductTitleByName(productName)}}" stepKey="dontSeeCorrectProductsOnStorefront"/> + </actionGroup> + <actionGroup name="StorefrontCheckProductPositionActionGroup"> + <arguments> + <argument name="position" type="string"/> + <argument name="productName" type="string"/> + </arguments> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber(position)}}" userInput="{{productName}}" stepKey="assertProductPosition"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml new file mode 100644 index 0000000000000..5c975998ab92e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- You must already be on the category page --> + <actionGroup name="StorefrontCheckProductPriceInCategoryActionGroup" extends="StorefrontCheckCategorySimpleProduct"> + <remove keyForRemoval="AssertProductPrice"/> + <see userInput="{{product.price}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(product.name)}}" stepKey="AssertProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml index fb2065d228d5a..1dcbc738c7651 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontClickAddToCartOnProductPageActionGroup"> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart" /> + <waitForPageLoad stepKey="waitForAddToCart"/> <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..e8be0db38fe2c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToCategoryPageActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontGoToCategoryPageActionGroup"> + <arguments> + <argument name="categoryName" type="string"/> + </arguments> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="onFrontend"/> + <waitForPageLoad stepKey="waitForStorefrontPageLoad"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(categoryName)}}" stepKey="toCategory"/> + <waitForPageLoad stepKey="waitForCategoryPage"/> + </actionGroup> + <actionGroup name="StorefrontGoToSubCategoryPageActionGroup" extends="StorefrontGoToCategoryPageActionGroup"> + <arguments> + <argument name="subCategoryName" type="string"/> + </arguments> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName(categoryName)}}" stepKey="toCategory"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(subCategoryName)}}" stepKey="openSubCategory" after="toCategory"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..39cb9ef1a63d4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontNavigateCategoryPageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Navigates storefront category page --> + <actionGroup name="StorefrontNavigateCategoryPageActionGroup"> + <arguments> + <argument name="category"/> + </arguments> + <!-- Open category page on storefront --> + <amOnPage url="{{StorefrontCategoryPage.url(category.custom_attributes[url_key])}}" stepKey="navigateStorefrontCategoryPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml new file mode 100644 index 0000000000000..692d1f4266b98 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenHomePageActionGroup"> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml new file mode 100644 index 0000000000000..7c79a624d4d11 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Action group opens product detail page from second website using Store code in URL option --> + <actionGroup name="StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup"> + <arguments> + <argument name="product"/> + <argument name="storeView"/> + </arguments> + <amOnPage url="/{{storeView.code}}/{{product.custom_attributes[url_key]}}.html" stepKey="openProductPageUsingStoreCodeInUrl"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{product.name}}" stepKey="assertProductName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml index 82042975d5fb8..e6392118f79b8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml @@ -13,12 +13,22 @@ <argument name="productName"/> </arguments> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdding"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdded"/> <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{productName}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> </actionGroup> + <actionGroup name="AddProductWithQtyToCartFromStorefrontProductPage" extends="addToCartFromStorefrontProductPage"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productQty" type="string"/> + </arguments> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="{{productQty}}" stepKey="fillProductQuantity" before="addToCart"/> + </actionGroup> + <!--Verify text length validation hint with multiple inputs--> <actionGroup name="testDynamicValidationHint"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml new file mode 100644 index 0000000000000..39793cb8f68de --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" extends="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="customOption" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductPageSection.customOptionDropDown}}" userInput="{{customOption}}" stepKey="selectCustomOption" before="productPriceAmount"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml new file mode 100644 index 0000000000000..6f7bdc46640b7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup" extends="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="customOption" type="entity"/> + <argument name="customOptionValue" type="entity"/> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsRadioButtons(customOption.title, customOptionValue.price)}}" stepKey="clickRadioButtonsProductOption" before="productPriceAmount"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeGroupData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeGroupData.xml new file mode 100644 index 0000000000000..4413cbcf86a96 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeGroupData.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="customGroup"> + <data key="name">Custom Group</data> + </entity> + <entity name="emptyGroup"> + <data key="name">Empty Group</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogInventoryConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogInventoryConfigData.xml new file mode 100644 index 0000000000000..c9b67e0db4398 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogInventoryConfigData.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CatalogInventoryOptionsShowOutOfStockEnable"> + <data key="path">cataloginventory/options/show_out_of_stock</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="CatalogInventoryOptionsShowOutOfStockDisable"> + <!-- Magento default value --> + <data key="path">cataloginventory/options/show_out_of_stock</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="CatalogInventoryItemOptionsBackordersEnable"> + <data key="path">cataloginventory/item_options/backorders</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="CatalogInventoryItemOptionsBackordersDisable"> + <!-- Magento default value --> + <data key="path">cataloginventory/item_options/backorders</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 27167d03d528e..13951a0d197d1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -110,4 +110,11 @@ <data key="is_active">false</data> <data key="include_in_menu">false</data> </entity> + <!-- Category from file "prepared-for-sample-data.csv"--> + <entity name="Gear" type="category"> + <data key="name">Gear</data> + <data key="name_lwr">gear</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index 389c41abf0bd1..1684bd0c8a2c3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -35,4 +35,20 @@ <data key="attribute_code">news_from_date</data> <data key="value">2018-05-17 00:00:00</data> </entity> + <entity name="CustomDynamicProductDescription" type="custom_attribute"> + <data key="attribute_code">description</data> + <data key="value">Dynamicscription testing 321</data> + </entity> + <entity name="CustomDynamicProductShortDescription" type="custom_attribute"> + <data key="attribute_code">short_description</data> + <data key="value">Short dynamictest 555</data> + </entity> + <entity name="CustomFixedProductDescription" type="custom_attribute"> + <data key="attribute_code">description</data> + <data key="value">Fixedscription testing 321</data> + </entity> + <entity name="CustomFixedProductShortDescription" type="custom_attribute"> + <data key="attribute_code">short_description</data> + <data key="value">Short Fixedtest 555</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/GroupPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/GroupPriceData.xml new file mode 100644 index 0000000000000..ae4736a1ca234 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/GroupPriceData.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="simpleGroupPrice" type="data"> + <data key="price">90.00</data> + <data key="price_type">fixed</data> + <data key="website_id">0</data> + <data key="customer_group">ALL GROUPS</data> + <data key="quantity">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 817dd637f81dd..653ec843cab3a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -320,4 +320,31 @@ <data key="option1_admin" unique="suffix">opt1'Admin</data> <data key="option1_frontend" unique="suffix">opt1'Front</data> </entity> + <entity name="productTextEditorAttribute" type="ProductAttribute"> + <data key="attribute_code" unique="suffix">attribute</data> + <data key="frontend_input">texteditor</data> + <data key="scope">global</data> + <data key="is_required">false</data> + <data key="is_unique">false</data> + <data key="is_searchable">true</data> + <data key="is_visible">true</data> + <data key="backend_type">text</data> + <data key="is_wysiwyg_enabled">true</data> + <data key="is_visible_in_advanced_search">true</data> + <data key="is_visible_on_front">true</data> + <data key="is_filterable">true</data> + <data key="is_filterable_in_search">true</data> + <data key="used_in_product_listing">true</data> + <data key="is_used_for_promo_rules">true</data> + <data key="is_comparable">true</data> + <data key="is_used_in_grid">true</data> + <data key="is_visible_in_grid">true</data> + <data key="is_filterable_in_grid">true</data> + <data key="used_for_sort_by">true</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> + </entity> + <entity name="VisualSwatchProductAttribute" type="ProductAttribute"> + <data key="frontend_input">swatch_visual</data> + <data key="attribute_code" unique="suffix">visual_swatch</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml new file mode 100644 index 0000000000000..99908f1c9df5f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="UpdateAttributeNameAndDescription" type="productAttributeMassUpdate"> + <data key="name" unique="suffix">New Bundle Product Name</data> + <data key="description" unique="suffix">This is the description</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index b8d6ec8e63e71..0b6a1e7d04439 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -153,6 +153,18 @@ <data key="status">1</data> <data key="quantity">0</data> </entity> + <entity name="SimpleProductInStockQuantityZero" type="product"> + <data key="sku" unique="suffix">testSku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">SimpleProductInStockQuantityZero</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">SimpleProductInStockQuantityZero</data> + <data key="status">1</data> + <data key="quantity">0</data> + <requiredEntity type="product_extension_attribute">EavStock0</requiredEntity> + </entity> <!-- Simple Product Disabled --> <entity name="SimpleProductOffline" type="product2"> <data key="sku" unique="suffix">testSku</data> @@ -167,6 +179,28 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <entity name="SimpleProductDisabled" type="product"> + <data key="sku" unique="suffix">simple_product_disabled</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Simple Product Disabled</data> + <data key="price">123.00</data> + <data key="visibility">4</data> + <data key="status">2</data> + <data key="quantity">1001</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> + <entity name="SimpleProductNotVisibleIndividually" type="product"> + <data key="sku" unique="suffix">simple_product_not_visible_individually</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Simple Product Not Visible Individually</data> + <data key="price">123.00</data> + <data key="visibility">1</data> + <data key="status">1</data> + <data key="quantity">1000</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> <entity name="NewSimpleProduct" type="product"> <data key="price">321.00</data> </entity> @@ -206,6 +240,9 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <entity name="ApiSimpleProductWithShortSKU" type="product2" extends="ApiSimpleOne"> + <data key="sku" unique="suffix">pr</data> + </entity> <entity name="ApiSimpleOneHidden" type="product2"> <data key="sku" unique="suffix">api-simple-product</data> <data key="type_id">simple</data> @@ -437,15 +474,44 @@ <requiredEntity type="product_option">ProductOptionDateTime</requiredEntity> <requiredEntity type="product_option">ProductOptionTime</requiredEntity> </entity> + <entity name="productWithOptionRadiobutton" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionRadiobuttonWithTwoFixedOptions</requiredEntity> + </entity> + <entity name="productWithCustomOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <data key="file">magento.jpg</data> + <requiredEntity type="product_option">ProductOptionDropDown2</requiredEntity> + </entity> + <entity name="productWithFixedOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <data key="file">magento.jpg</data> + <requiredEntity type="product_option">ProductOptionRadioButton2</requiredEntity> + </entity> <entity name="productWithOptions2" type="product"> <var key="sku" entityType="product" entityKey="sku" /> <requiredEntity type="product_option">ProductOptionDropDownWithLongValuesTitle</requiredEntity> </entity> + <entity name="productWithDropdownOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionValueDropdown</requiredEntity> + </entity> + <entity name="productWithDropdownAndFieldOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionValueDropdown</requiredEntity> + <requiredEntity type="product_option">ProductOptionField</requiredEntity> + </entity> <entity name="ProductWithTextFieldAndAreaOptions" type="product"> <var key="sku" entityType="product" entityKey="sku" /> <requiredEntity type="product_option">ProductOptionField</requiredEntity> <requiredEntity type="product_option">ProductOptionArea</requiredEntity> </entity> + <entity name="ProductWithTextFieldAndAreaAndFileOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionField</requiredEntity> + <requiredEntity type="product_option">ProductOptionArea</requiredEntity> + <requiredEntity type="product_option">ProductOptionFile</requiredEntity> + </entity> <entity name="ApiVirtualProductWithDescription" type="product"> <data key="sku" unique="suffix">api-virtual-product</data> <data key="type_id">virtual</data> @@ -995,4 +1061,108 @@ <data key="tax_class_id">2</data> <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> </entity> + <entity name="defaultSimpleProductWeight200" type="product"> + <data key="name" unique="suffix">Testp</data> + <data key="sku" unique="suffix">testsku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="price">560.00</data> + <data key="urlKey" unique="suffix">testurl-</data> + <data key="status">1</data> + <data key="quantity">25</data> + <data key="weight">200</data> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> + </entity> + <entity name="SimpleProductWithTwoOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionMultiSelect</requiredEntity> + </entity> + <entity name="SimpleProductWithOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionPercentPriceDropDown</requiredEntity> + </entity> + <entity name="SimpleProductWithDescription" type="product"> + <data key="sku" unique="suffix">productwithdescription</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">ProductWithDescription</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">productwithdescription</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + </entity> + <entity name="SimpleProductWithSpecialPrice" type="product"> + <data key="sku" unique="suffix">SimpleProductWithSpecialPrice</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">SimpleProduct</data> + <data key="price">100.00</data> + <data key="special_price">90.00</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity">86</data> + <data key="urlKey" unique="suffix">simpleproduct</data> + <data key="weight">1</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="SimpleProductWithSpecialPriceSecond" type="product"> + <data key="sku" unique="suffix">SimpleProductWithSpecialPriceSecond</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">SimpleProduct</data> + <data key="price">150.00</data> + <data key="special_price">110.00</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="quantity">86</data> + <data key="urlKey" unique="suffix">simpleproduct</data> + <data key="weight">1</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="SimpleProduct_100" type="product"> + <data key="sku" unique="suffix">testSku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">testProductName</data> + <data key="price">100.00</data> + <data key="urlKey" unique="suffix">testurlkey</data> + <data key="status">1</data> + <data key="quantity">777</data> + <data key="weight">1</data> + <requiredEntity type="product_extension_attribute">EavStock777</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="ApiSimpleOneQty10" type="product2"> + <data key="sku" unique="suffix">api-simple-product</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Simple Product</data> + <data key="price">40.00</data> + <data key="urlKey" unique="suffix">api-simple-product</data> + <data key="status">1</data> + <data key="quantity">10</data> + <requiredEntity type="product_extension_attribute">EavStock10</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> + <entity name="ApiSimpleTwoQty10" type="product2"> + <data key="sku" unique="suffix">api-simple-product-two</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Simple Product Two</data> + <data key="price">40.00</data> + <data key="urlKey" unique="suffix">api-simple-product-two</data> + <data key="status">1</data> + <data key="quantity">10</data> + <requiredEntity type="product_extension_attribute">EavStock10</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml new file mode 100644 index 0000000000000..42e85ab269884 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductDesignData.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="simpleBlankDesign" type="product"> + <data key="custom_design">Magento Blank</data> + <data key="page_layout">2 columns with left bar</data> + <data key="options_container">Product Info Column</data> + </entity> + <entity name="simpleLumaDesign" type="product"> + <data key="custom_design">Magento Luma</data> + <data key="page_layout">Empty</data> + <data key="options_container">Block after Info Column</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml index e9e9e43752365..5a6a0b5dd9518 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml @@ -20,4 +20,10 @@ <entity name="EavStock1" type="product_extension_attribute"> <requiredEntity type="stock_item">Qty_1</requiredEntity> </entity> + <entity name="EavStock0" type="product_extension_attribute"> + <requiredEntity type="stock_item">Qty_0</requiredEntity> + </entity> + <entity name="EavStock777" type="product_extension_attribute"> + <requiredEntity type="stock_item">Qty_777</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml index ca5024920ad40..720087917aad4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml @@ -38,6 +38,16 @@ <data key="price_type">percent</data> <data key="max_characters">0</data> </entity> + <entity name="ProductOptionAreaFixed" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionArea</data> + <data key="type">area</data> + <data key="is_require">false</data> + <data key="sort_order">2</data> + <data key="price">100</data> + <data key="price_type">fixed</data> + <data key="max_characters">0</data> + </entity> <entity name="ProductOptionFile" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionFile</data> @@ -59,6 +69,16 @@ <requiredEntity type="product_option_value">ProductOptionValueDropdown1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueDropdown2</requiredEntity> </entity> + <entity name="ProductOptionDropDown2" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">false</data> + <requiredEntity type="product_option_value">ProductOptionValueDropdown1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueDropdown3</requiredEntity> + </entity> + <entity name="ProductOptionDropDownWithLongValuesTitle" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionDropDownWithLongTitles</data> @@ -68,6 +88,15 @@ <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle2</requiredEntity> </entity> + <entity name="ProductOptionValueDropdown" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionValueWithSkuDropdown1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueWithSkuDropdown2</requiredEntity> + </entity> <entity name="ProductOptionRadiobutton" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionRadioButtons</data> @@ -77,6 +106,24 @@ <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueRadioButtons2</requiredEntity> </entity> + <entity name="ProductOptionRadioButton2" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionRadioButtons</data> + <data key="type">radio</data> + <data key="sort_order">4</data> + <data key="is_require">false</data> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons4</requiredEntity> + </entity> + <entity name="ProductOptionRadiobuttonWithTwoFixedOptions" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionRadioButtons</data> + <data key="type">radio</data> + <data key="sort_order">5</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons3</requiredEntity> + </entity> <entity name="ProductOptionCheckbox" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionCheckbox</data> @@ -121,4 +168,18 @@ <data key="price">0.00</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionPercentPriceDropDown" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionPercentPriceValueDropdown</requiredEntity> + </entity> + <entity name="ProductOptionFieldSecond" extends="ProductOptionField"> + <data key="title" unique="suffix">fifth option</data> + </entity> + <entity name="ProductOptionFileSecond" extends="ProductOptionFile"> + <data key="title" unique="suffix">fourth option</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml index d16a201cd9ecc..e738994357366 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml @@ -32,6 +32,24 @@ <data key="price">99.99</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionValueDropdown3" type="product_option_value"> + <data key="title">OptionValueDropDown3</data> + <data key="sort_order">2</data> + <data key="price">10</data> + <data key="price_type">percent</data> + </entity> + <entity name="ProductOptionValueRadioButtons4" type="product_option_value"> + <data key="title">OptionValueRadioButtons4</data> + <data key="sort_order">1</data> + <data key="price">9.99</data> + <data key="price_type">fixed</data> + </entity> + <entity name="ProductOptionValueRadioButtons3" type="product_option_value"> + <data key="title">OptionValueRadioButtons3</data> + <data key="sort_order">3</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + </entity> <entity name="ProductOptionValueCheckbox" type="product_option_value"> <data key="title">OptionValueCheckbox</data> <data key="sort_order">1</data> @@ -62,4 +80,25 @@ <data key="price">20</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionPercentPriceValueDropdown" type="product_option_value"> + <data key="title">40 Percent</data> + <data key="sort_order">0</data> + <data key="price">40</data> + <data key="price_type">percent</data> + <data key="sku">sku_drop_down_row_1</data> + </entity> + <entity name="ProductOptionValueWithSkuDropdown1" type="product_option_value"> + <data key="title">ProductOptionValueWithSkuDropdown1</data> + <data key="sort_order">1</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + <data key="sku">product_option_value_sku_dropdown_1_</data> + </entity> + <entity name="ProductOptionValueWithSkuDropdown2" type="product_option_value"> + <data key="title">ProductOptionValueWithSkuDropdown2</data> + <data key="sort_order">1</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + <data key="sku">product_option_value_sku_dropdown_2_</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml index 7cba4c3c76fe9..32f4dc1404dd7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml @@ -32,4 +32,12 @@ <data key="qty">1</data> <data key="is_in_stock">true</data> </entity> + <entity name="Qty_0" type="stock_item"> + <data key="qty">0</data> + <data key="is_in_stock">true</data> + </entity> + <entity name="Qty_777" type="stock_item"> + <data key="qty">777</data> + <data key="is_in_stock">true</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml index e5070340421a9..0c88c666a20ca 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml @@ -56,4 +56,12 @@ <data key="quantity">2</data> <var key="sku" entityType="product2" entityKey="sku" /> </entity> + <entity name="tierProductPriceDefault" type="catalogTierPrice"> + <data key="price">90.00</data> + <data key="price_type">fixed</data> + <data key="website_id">0</data> + <data key="customer_group">ALL GROUPS</data> + <data key="quantity">30</data> + <var key="sku" entityType="product" entityKey="sku" /> + </entity> </entities> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml new file mode 100644 index 0000000000000..7bb8cf5f4db37 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/WYSIWYGConfigData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableWYSIWYG"> + <data key="path">cms/wysiwyg/enabled</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">enabled</data> + </entity> + <entity name="EnableTinyMCE4"> + <data key="path">cms/wysiwyg/editor</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml index 977e63b9ec927..92961cc48212a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml @@ -49,7 +49,7 @@ </section> <section name="CatalogWYSIWYGSection"> <element name="ShowHideBtn" type="button" selector="#togglecategory_form_description"/> - <element name="TinyMCE4" type="text" selector=".mce-branding-powered-by"/> + <element name="TinyMCE4" type="text" selector=".mce-branding"/> <element name="Style" type="button" selector=".mce-txt" /> <element name="Bold" type="button" selector=".mce-i-bold" /> <element name="Italic" type="button" selector=".mce-i-italic" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml index df79ec61ef736..6618b0e1a48d8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml @@ -17,5 +17,6 @@ <element name="productGridNameProduct" type="text" selector="//table[@id='catalog_category_products_table']//td[contains(., '{{productName}}')]" parameterized="true"/> <element name="productVisibility" type="select" selector="//*[@name='product[visibility]']"/> <element name="productSelectAll" type="checkbox" selector="input.admin__control-checkbox"/> + <element name="productsGridEmpty" type="text" selector="#catalog_category_products_table .data-grid-tr-no-data .empty-text"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml index 3c05f72ff1597..e9ff40f98bb16 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryProductsSection"> <element name="sectionHeader" type="button" selector="div[data-index='assign_products']" timeout="30"/> + <element name="addProducts" type="button" selector="#catalog_category_add_product_tabs" timeout="30"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index d24c501152b78..4c60ebe78b882 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -13,13 +13,14 @@ <element name="DefaultLabel" type="input" selector="#attribute_label"/> <element name="InputType" type="select" selector="#frontend_input"/> <element name="ValueRequired" type="select" selector="#is_required"/> + <element name="UpdateProductPreviewImage" type="select" selector="[name='update_product_preview_image']"/> <element name="AdvancedProperties" type="button" selector="#advanced_fieldset-wrapper"/> <element name="DefaultValue" type="input" selector="#default_value_text"/> <element name="Scope" type="select" selector="#is_global"/> <element name="Save" type="button" selector="#save" timeout="30"/> <element name="DeleteAttribute" type="button" selector="#delete" timeout="30"/> <element name="SaveAndEdit" type="button" selector="#save_and_edit_button" timeout="30"/> - <element name="TinyMCE4" type="button" selector="//span[text()='Default Value']/parent::label/following-sibling::div//div[@class='mce-branding-powered-by']"/> + <element name="TinyMCE4" type="button" selector="//span[text()='Default Value']/parent::label/following-sibling::div//*[contains(@class,'mce-branding')]"/> <element name="checkIfTabOpen" selector="//div[@id='advanced_fieldset-wrapper' and not(contains(@class,'opened'))]" type="button"/> <element name="useInLayeredNavigation" type="select" selector="#is_filterable"/> <element name="addSwatch" type="button" selector="#add_new_swatch_text_option_button"/> @@ -47,6 +48,7 @@ <element name="EnableWYSIWYG" type="select" selector="#enabled"/> <element name="useForPromoRuleConditions" type="select" selector="#is_used_for_promo_rules"/> <element name="StorefrontPropertiesSectionToggle" type="button" selector="#front_fieldset-wrapper"/> + <element name="visibleOnCatalogPagesOnStorefront" type="select" selector="#is_visible_on_front"/> </section> <section name="WYSIWYGProductAttributeSection"> <element name="ShowHideBtn" type="button" selector="#toggledefault_value_texteditor"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml index 63bdcd52cdd20..b243fbfd6034a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml @@ -21,5 +21,6 @@ <element name="ProductDataMayBeLostModal" type="button" selector="//aside[contains(@class,'_show')]//header[contains(.,'Product data may be lost')]"/> <element name="ProductDataMayBeLostConfirmButton" type="button" selector="//aside[contains(@class,'_show')]//button[.='Change Input Type']"/> <element name="defaultLabel" type="text" selector="//td[contains(text(), '{{attributeName}}')]/following-sibling::td[contains(@class, 'col-frontend_label')]" parameterized="true"/> + <element name="formByStoreId" type="block" selector="//form[contains(@action,'store/{{store_id}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml index 0814c7ea7dc3e..df8915a499843 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml @@ -17,9 +17,16 @@ <element name="assignedAttribute" type="text" selector="//*[@id='tree-div1']//span[text()='{{attributeName}}']" parameterized="true"/> <element name="xThLineItemYthAttributeGroup" type="text" selector="//*[@id='tree-div1']/ul/div/li[{{y}}]//li[{{x}}]" parameterized="true"/> <element name="xThLineItemAttributeGroup" type="text" selector="//*[@id='tree-div1']//span[text()='{{groupName}}']/parent::*/parent::*/parent::*//li[{{x}}]//a/span" parameterized="true"/> + <element name="attributesInGroup" type="text" selector="//span[text()='{{GroupName}}']/../../following-sibling::ul/li" parameterized="true"/> <!-- Unassigned Attributes Column --> <element name="unassignedAttributesTree" type="block" selector="#tree-div2"/> <element name="unassignedAttribute" type="text" selector="//*[@id='tree-div2']//span[text()='{{attributeName}}']" parameterized="true"/> <element name="xThLineItemUnassignedAttribute" type="text" selector="//*[@id='tree-div2']//li[{{x}}]//a/span" parameterized="true"/> + <!-- Buttons --> + <element name="AddNewGroup" type="button" selector="button[data-ui-id='adminhtml-catalog-product-set-edit-add-group-button']"/> + <!-- Modal Window Add New Group --> + <element name="newGroupName" type="input" selector="input[data-role='promptField']"/> + <element name="buttonOk" type="button" selector=".modal-footer .action-primary.action-accept"/> + <element name="errorLabel" type="text" selector="label.mage-error"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index 352d219351fb8..6d4d5d86ef798 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductCustomizableOptionsSection"> - <element name="checkIfCustomizableOptionsTabOpen" type="text" selector="//span[text()='Customizable Options']/parent::strong/parent::*[@data-state-collapsible='closed']"/> + <element name="checkIfCustomizableOptionsTabOpen" type="text" selector="//span[text()='Customizable Options']/parent::strong/parent::*[@data-state-collapsible='closed']" timeout="30"/> <element name="customizableOptions" type="text" selector="//strong[contains(@class, 'admin__collapsible-title')]/span[text()='Customizable Options']"/> <element name="useDefaultOptionTitle" type="text" selector="[data-index='options'] tr.data-row [data-index='title'] [name^='options_use_default']"/> <element name="useDefaultOptionTitleByIndex" type="text" selector="[data-index='options'] [data-index='values'] tr[data-repeat-index='{{var1}}'] [name^='options_use_default']" parameterized="true"/> @@ -19,10 +19,14 @@ <element name="optionTypeOpenDropDown" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-select" timeout="30"/> <element name="optionTypeTextField" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-menu._active li li" timeout="30"/> <element name="maxCharactersInput" type="input" selector="input[name='product[options][0][max_characters]']"/> + <element name="deleteCustomOptions" type="button" selector="//div[contains(@class, 'fieldset-wrapper-title')]//span[contains(., '{{optionTitle}}')]/parent::div/parent::div//button[@class='action-delete']" parameterized="true" timeout="30"/> + <element name="customOption" type="block" selector="[data-index='options'] tbody tr.data-row"/> + <element name="customOptionButtonDelete" type="button" selector="[data-index='options'] [data-index='delete_button']"/> - <element name="optionTypeDropDown" type="select" selector="//table[@data-index='options']//tr[{{index}}]//div[@data-index='type']//div[contains(@class, 'action-select-wrap')]" parameterized="true" /> - <element name="optionTypeItem" type="select" selector="//table[@data-index='options']//tr[{{index}}]//div[@data-index='type']//*[contains(@class, 'action-menu-item')]//*[contains(., '{{optionValue}}')]" parameterized="true" /> + <element name="optionTypeDropDown" type="select" selector="//table[@data-index='options']//tr[{{index}}]//div[@data-index='type']//div[contains(@class, 'action-select-wrap')]" parameterized="true" timeout="30"/> + <element name="optionTypeItem" type="select" selector="//table[@data-index='options']//tr[{{index}}]//div[@data-index='type']//*[contains(@class, 'action-menu-item')]//*[contains(., '{{optionValue}}')]" parameterized="true" timeout="30"/> <element name="checkSelect" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Type']/parent::label/parent::div/parent::div//div[@data-role='selected-option']" parameterized="true"/> + <element name="checkOptionType" type="select" selector="//span[text()='{{optionTitle}}']/parent::div/parent::div/parent::div//parent::label/parent::div/parent::div//li[@class='admin__action-multiselect-menu-inner-item']//label[text()='{{optionType}}']" parameterized="true"/> <element name="checkDropDown" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//parent::label/parent::div/parent::div//li[@class='admin__action-multiselect-menu-inner-item']//label[text()='Drop-down']" parameterized="true"/> <element name="clickAddValue" type="button" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tfoot//button" parameterized="true"/> <element name="fillOptionValueTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody/tr[@data-repeat-index='{{var2}}']//span[text()='Title']/parent::label/parent::div/parent::div//div[@class='admin__field-control']/input" parameterized="true"/> @@ -32,10 +36,17 @@ <element name="checkboxUseDefaultOption" type="checkbox" selector="//table[@data-index='values']//tbody//tr[@data-repeat-index='{{var1}}']//div[@class='admin__field-control']//input[@type='checkbox']" parameterized="true"/> <element name="requiredCheckBox" type="checkbox" selector="input[name='product[options][{{index}}][is_require]']" parameterized="true" /> <element name="fillOptionValueSku" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody/tr[@data-repeat-index='{{var2}}']//span[text()='SKU']/parent::label/parent::div/parent::div//div[@class='admin__field-control']/input" parameterized="true"/> + <element name="fillOptionCompatibleFileExtensions" type="input" selector="input[name='product[options][{{index}}][file_extension]']" parameterized="true"/> <!-- Elements that make it easier to select the most recently added element --> + <element name="optionPriceByTitle" type="input" selector="//*[@data-index='options']//*[@data-role='collapsible-title' and contains(., '{{optionTitle}}')]/ancestor::tr//*[@data-index='price']//input" parameterized="true"/> + <element name="optionPriceTypeByTitle" type="select" selector="//*[@data-index='options']//*[@data-role='collapsible-title' and contains(., '{{optionTitle}}')]/ancestor::tr//*[@data-index='price_type']//select" parameterized="true"/> + <element name="optionSkuByTitle" type="input" selector="//*[@data-index='options']//*[@data-role='collapsible-title' and contains(., '{{optionTitle}}')]/ancestor::tr//*[@data-index='sku']//input" parameterized="true"/> + <element name="optionFileExtensionByTitle" type="input" selector="//*[@data-index='options']//*[@data-role='collapsible-title' and contains(., '{{optionTitle}}')]/ancestor::tr//*[@data-index='file_extension']//input" parameterized="true"/> <element name="lastOptionTitle" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, '_required')]//input" /> <element name="lastOptionTypeParent" type="block" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__action-multiselect-text')]" /> + <element name="lastOptionPrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@name, '[price]')]"/> + <element name="lastOptionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@name, '[price_type]')]"/> <!-- var 1 represents the option type that you want to select, i.e "radio buttons" --> <element name="optionType" type="block" selector="//*[@data-index='custom_options']//label[text()='{{var1}}'][ancestor::*[contains(@class, '_active')]]" parameterized="true" /> <element name="addValue" type="button" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@data-action='add_new_row']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml index bc7c472df6eac..4196a86fe25db 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml @@ -18,7 +18,7 @@ <element name="qtyIncrementsUseConfigSettings" type="checkbox" selector="//input[@name='product[stock_data][use_config_qty_increments]']"/> <element name="doneButton" type="button" selector="//aside[contains(@class,'product_form_product_form_advanced_inventory_modal')]//button[contains(@data-role,'action')]" timeout="5"/> <element name="useConfigSettings" type="checkbox" selector="//input[@name='product[stock_data][use_config_manage_stock]']"/> - <element name="manageStock" type="select" selector="//*[@name='product[stock_data][manage_stock]']"/> + <element name="manageStock" type="select" selector="//*[@name='product[stock_data][manage_stock]']" timeout="30"/> <element name="advancedInventoryCloseButton" type="button" selector=".product_form_product_form_advanced_inventory_modal button.action-close" timeout="30"/> <element name="miniQtyConfigSetting" type="checkbox" selector="//*[@name='product[stock_data][use_config_min_sale_qty]']"/> <element name="miniQtyAllowedInCart" type="input" selector="//*[@name='product[stock_data][min_sale_qty]']"/> @@ -28,5 +28,7 @@ <element name="notifyBelowQty" type="input" selector="//*[@name='product[stock_data][notify_stock_qty]']"/> <element name="advancedInventoryQty" type="input" selector="//div[@class='modal-inner-wrap']//input[@name='product[quantity_and_stock_status][qty]']"/> <element name="advancedInventoryStockStatus" type="select" selector="//div[@class='modal-inner-wrap']//select[@name='product[quantity_and_stock_status][is_in_stock]']"/> + <element name="outOfStockThreshold" type="select" selector="//*[@name='product[stock_data][min_qty]']" timeout="30"/> + <element name="minQtyConfigSetting" type="checkbox" selector="//input[@name='product[stock_data][use_config_min_qty]']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml index 3ef78a3fe8f41..77b89a07fb76a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormAdvancedPricingSection"> <element name="customerGroupPriceAddButton" type="button" selector="[data-action='add_new_row']" timeout="30"/> + <element name="addCustomerGroupPrice" type="button" selector="//span[text()='Add']/ancestor::button" timeout="30"/> <element name="customerGroupPriceDeleteButton" type="button" selector="[data-action='remove_row']" timeout="30"/> <element name="advancedPricingCloseButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-close" timeout="30"/> <element name="productTierPriceWebsiteSelect" type="select" selector="[name='product[tier_price][{{var1}}][website_id]']" parameterized="true"/> @@ -20,8 +21,9 @@ <element name="productTierPriceFixedPriceInput" type="input" selector="[name='product[tier_price][{{var1}}][price]']" parameterized="true"/> <element name="productTierPricePercentageValuePriceInput" type="input" selector="[name='product[tier_price][{{var1}}][percentage_value]']" parameterized="true"/> <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> - <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="5"/> + <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="30"/> <element name="msrp" type="input" selector="//input[@name='product[msrp]']" timeout="30"/> - <element name="save" type="button" selector="#save-button"/> + <element name="msrpType" type="select" selector="//select[@name='product[msrp_display_actual_price_type]']" timeout="30"/> + <element name="save" type="button" selector="#save-button" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml index a2a349ed67611..b0aee1795dc3b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml @@ -25,6 +25,7 @@ <section name="AdminProductFormNewAttributeAdvancedSection"> <element name="sectionHeader" type="button" selector="div[data-index='advanced_fieldset']"/> <element name="defaultValue" type="textarea" selector="input[name='default_value_text']"/> + <element name="scope" type="select" selector="//div[@data-index='advanced_fieldset']//select[@name='is_global']"/> </section> <section name="AdminProductFormNewAttributeStorefrontSection"> <element name="sectionHeader" type="button" selector="div[data-index='front_fieldset']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index f515171e835db..80b4159167453 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -21,6 +21,7 @@ <element name="enableProductAttributeLabel" type="text" selector="//span[text()='Enable Product']/parent::label"/> <element name="enableProductAttributeLabelWrapper" type="text" selector="//span[text()='Enable Product']/parent::label/parent::div"/> <element name="productStatus" type="checkbox" selector="input[name='product[status]']"/> + <element name="productStatusValue" type="checkbox" selector="input[name='product[status]'][value='{{value}}']" timeout="30" parameterized="true"/> <element name="productStatusDisabled" type="checkbox" selector="input[name='product[status]'][disabled]"/> <element name="enableProductLabel" type="checkbox" selector="input[name='product[status]']+label"/> <element name="productStatusUseDefault" type="checkbox" selector="input[name='use_default[status]']"/> @@ -32,11 +33,12 @@ <element name="productTaxClassDisabled" type="select" selector="select[name='product[tax_class_id]'][disabled=true]"/> <element name="productTaxClassUseDefault" type="checkbox" selector="input[name='use_default[tax_class_id]']"/> <element name="advancedPricingLink" type="button" selector="button[data-index='advanced_pricing_button']" timeout="30"/> - <element name="categoriesDropdown" type="multiselect" selector="div[data-index='category_ids']"/> + <element name="currentCategory" type="text" selector=".admin__action-multiselect-crumb > span"/> + <element name="categoriesDropdown" type="multiselect" selector="div[data-index='category_ids']" timeout="30"/> <element name="unselectCategories" type="button" selector="//span[@class='admin__action-multiselect-crumb']/span[contains(.,'{{category}}')]/../button[@data-action='remove-selected-item']" parameterized="true" timeout="30"/> <element name="productQuantity" type="input" selector=".admin__field[data-index=qty] input"/> <element name="advancedInventoryLink" type="button" selector="//button[contains(@data-index, 'advanced_inventory_button')]" timeout="30"/> - <element name="productStockStatus" type="select" selector="select[name='product[quantity_and_stock_status][is_in_stock]']"/> + <element name="productStockStatus" type="select" selector="select[name='product[quantity_and_stock_status][is_in_stock]']" timeout="30"/> <element name="productStockStatusDisabled" type="select" selector="select[name='product[quantity_and_stock_status][is_in_stock]'][disabled=true]"/> <element name="stockStatus" type="select" selector="[data-index='product-details'] select[name='product[quantity_and_stock_status][is_in_stock]']"/> <element name="productWeight" type="input" selector=".admin__field[data-index=weight] input"/> @@ -46,7 +48,7 @@ <element name="priceFieldError" type="text" selector="//input[@name='product[price]']/parent::div/parent::div/label[@class='admin__field-error']"/> <element name="addAttributeBtn" type="button" selector="#addAttribute"/> <element name="createNewAttributeBtn" type="button" selector="button[data-index='add_new_attribute_button']"/> - <element name="save" type="button" selector="#save-button"/> + <element name="save" type="button" selector="#save-button" timeout="30"/> <element name="saveNewAttribute" type="button" selector="//aside[contains(@class, 'create_new_attribute_modal')]//button[@id='save']"/> <element name="successMessage" type="text" selector="#messages"/> <element name="attributeTab" type="button" selector="//strong[contains(@class, 'admin__collapsible-title')]/span[text()='Attributes']"/> @@ -64,20 +66,25 @@ <element name="attributeRequiredInput" type="input" selector="//input[contains(@name, 'product[{{attributeCode}}]')]" parameterized="true"/> <element name="attributeFieldError" type="text" selector="//*[@class='admin__field _required _error']/..//label[contains(.,'This is a required field.')]"/> <element name="customSelectField" type="select" selector="//select[@name='product[{{var}}]']" parameterized="true"/> - <element name="searchCategory" type="input" selector="//*[@data-index='category_ids']//input[contains(@class, 'multiselect-search')]"/> - <element name="selectCategory" type="input" selector="//*[@data-index='category_ids']//label[contains(., '{{categoryName}}')]" parameterized="true"/> + <element name="searchCategory" type="input" selector="//*[@data-index='category_ids']//input[contains(@class, 'multiselect-search')]" timeout="30"/> + <element name="selectCategory" type="input" selector="//*[@data-index='category_ids']//label[contains(., '{{categoryName}}')]" parameterized="true" timeout="30"/> <element name="done" type="button" selector="//*[@data-index='category_ids']//button[@data-action='close-advanced-select']" timeout="30"/> <element name="selectMultipleCategories" type="input" selector="//*[@data-index='container_category_ids']//*[contains(@class, '_selected')]"/> <element name="countryOfManufacture" type="select" selector="select[name='product[country_of_manufacture]']"/> <element name="newAddedAttribute" type="text" selector="//fieldset[@class='admin__fieldset']//div[contains(@data-index,'{{attributeCode}}')]" parameterized="true"/> + <element name="newCategoryButton" type="button" selector="button[data-index='create_category_button']" timeout="30"/> + <element name="footerBlock" type="block" selector="//footer"/> </section> <section name="ProductInWebsitesSection"> <element name="sectionHeader" type="button" selector="div[data-index='websites']" timeout="30"/> + <element name="sectionHeaderOpened" type="button" selector="[data-index='websites']._show" timeout="30"/> <element name="website" type="checkbox" selector="//label[contains(text(), '{{var1}}')]/parent::div//input[@type='checkbox']" parameterized="true"/> + <element name="websiteChecked" type="checkbox" selector="//label[contains(text(), '{{var1}}')]/parent::div//input[@type='checkbox'][@value='1']" parameterized="true"/> </section> <section name="ProductDesignSection"> <element name="DesignTab" type="button" selector="//strong[@class='admin__collapsible-title']//span[text()='Design']"/> <element name="LayoutDropdown" type="select" selector="select[name='product[page_layout]']"/> + <element name="productOptionsContainer" type="select" selector="select[name='product[options_container]']"/> </section> <section name="AdminProductFormRelatedUpSellCrossSellSection"> <element name="relatedProductsHeader" type="button" selector=".admin__collapsible-block-wrapper[data-index='related']" timeout="30"/> @@ -114,10 +121,10 @@ <element name="InsertImageIcon" type="button" selector="//div[contains(@id, '{{var1}}')]//i[@class='mce-ico mce-i-image']" parameterized="true"/> <element name="InsertTable" type="button" selector="//div[contains(@id, '{{var1}}')]//i[@class='mce-ico mce-i-table']" parameterized="true"/> <element name="SpecialCharacter" type="button" selector="//div[contains(@id, '{{var1}}')]//i[@class='mce-ico mce-i-charmap']" parameterized="true"/> - <element name="TinyMCE4" type="text" selector="//div[contains(@id, '{{var1}}')]//div[@class='mce-branding-powered-by']" parameterized="true"/> + <element name="TinyMCE4" type="text" selector="//div[contains(@id, '{{var1}}')]//*[contains(@class,'mce-branding')]" parameterized="true"/> </section> <section name="ProductDescriptionWYSIWYGToolbarSection"> - <element name="TinyMCE4" type ="button" selector="//div[@id='editorproduct_form_description']//div[@class='mce-branding-powered-by']" /> + <element name="TinyMCE4" type ="button" selector="//div[@id='editorproduct_form_description']//*[contains(@class,'mce-branding')]" /> <element name="showHideBtn" type="button" selector="#toggleproduct_form_description"/> <element name="InsertImageBtn" type="button" selector="#buttonsproduct_form_description > .scalable.action-add-image.plugin" /> <element name="Style" type="button" selector="//div[@id='editorproduct_form_description']//span[text()='Paragraph']" /> @@ -155,7 +162,7 @@ <element name="confirmDelete" type="button" selector=".action-primary.action-accept" /> </section> <section name="ProductShortDescriptionWYSIWYGToolbarSection"> - <element name="TinyMCE4" type ="button" selector="//div[@id='editorproduct_form_short_description']//div[@class='mce-branding-powered-by']" /> + <element name="TinyMCE4" type ="button" selector="//div[@id='editorproduct_form_short_description']//*[contains(@class,'mce-branding')]" /> <element name="InsertImageBtn" type="button" selector="#buttonsproduct_form_short_description > .scalable.action-add-image.plugin" /> <element name="showHideBtn" type="button" selector="#toggleproduct_form_short_description"/> <element name="Style" type="button" selector="//div[@id='editorproduct_form_short_description']//span[text()='Paragraph']" /> @@ -192,13 +199,14 @@ </section> <section name="ProductDescriptionWysiwygSection"> <element name="EditArea" type="text" selector="#editorproduct_form_description .mce-edit-area"/> + <element name="attributeEditArea" type="textarea" selector="#product_form_{{attributeCode}}" parameterized="true" timeout="30"/> </section> <section name="ProductShortDescriptionWysiwygSection"> <element name="EditArea" type="text" selector="#editorproduct_form_short_description .mce-edit-area"/> </section> <section name="AdminProductFormAdvancedPricingSection"> <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> - <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary"/> + <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="30"/> <element name="useDefaultPrice" type="checkbox" selector="//input[@name='product[special_price]']/parent::div/following-sibling::div/input[@name='use_default[special_price]']"/> </section> <section name="AdminProductAttributeSection"> @@ -207,5 +215,7 @@ <element name="textAttributeByName" type="text" selector="//div[@data-index='attributes']//fieldset[contains(@class, 'admin__field') and .//*[contains(.,'{{var}}')]]//input" parameterized="true"/> <element name="dropDownAttribute" type="select" selector="//select[@name='product[{{arg}}]']" parameterized="true" timeout="30"/> <element name="attributeSection" type="block" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> + <element name="attributeGroupByName" type="button" selector="//div[@class='fieldset-wrapper-title']//span[text()='{{group}}']" parameterized="true"/> + <element name="attributeByGroupAndName" type="text" selector="//div[@class='fieldset-wrapper-title']//span[text()='{{group}}']/../../following-sibling::div//span[contains(text(),'attribute')]" parameterized="true"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml new file mode 100644 index 0000000000000..63b745e522705 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGiftOptionsSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminProductGiftOptionsSection"> + <element name="giftOptions" type="text" selector="div[data-index='gift-options']"/> + <element name="useConfigSettingsMessage" type="checkbox" selector="[name='product[use_config_gift_message_available]']"/> + <element name="toggleProductGiftMessage" type="button" selector="input[name='product[gift_message_available]']+label"/> + <element name="giftMessageStatus" type="checkbox" selector="input[name='product[gift_message_available]'][value='{{status}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml index 3b74041684017..66e6f17be3430 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridActionSection"> <element name="addProductBtn" type="button" selector="#add_new_product-button" timeout="30"/> - <element name="addProductToggle" type="button" selector=".action-toggle.primary.add"/> + <element name="addProductToggle" type="button" selector=".action-toggle.primary.add" timeout="30"/> <element name="addSimpleProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-simple']" timeout="30"/> <element name="addGroupedProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-grouped']" timeout="30"/> <element name="addVirtualProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-virtual']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 939974248aabf..3b6f24c0f259d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -35,5 +35,6 @@ <element name="nthRow" type="block" selector=".data-row:nth-of-type({{var}})" parameterized="true" timeout="30"/> <element name="productCount" type="text" selector="#catalog_category_products-total-count"/> <element name="productPerPage" type="select" selector="#catalog_category_products_page-limit"/> + <element name="storeViewDropdown" type="text" selector="//select[@name='store_id']/option[contains(.,'{{storeView}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml index ef596bed186e5..f3b0d3a895cb1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml @@ -20,6 +20,7 @@ <element name="relatedDependent" type="block" selector="//div[@data-index='related']//div[contains(@class, '_show')]"/> <element name="selectedRelatedProduct" type="block" selector="//span[@data-index='name']"/> <element name="removeRelatedProduct" type="button" selector="//span[text()='Related Products']//..//..//..//span[text()='{{productName}}']//..//..//..//..//..//button[@class='action-delete']" parameterized="true"/> + <element name="selectedProductSku" type="text" selector="//div[@data-index='{{section}}']//span[@data-index='sku']" parameterized="true" timeout="30"/> </section> <section name="AdminAddUpSellProductsModalSection"> <element name="Modal" type="button" selector=".product_form_product_form_related_upsell_modal"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml index 53231a2a68633..8685e84a347f2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml @@ -12,5 +12,8 @@ <element name="sectionHeader" type="button" selector="div[data-index='search-engine-optimization']" timeout="30"/> <element name="urlKeyInput" type="input" selector="input[name='product[url_key]']"/> <element name="useDefaultUrl" type="checkbox" selector="input[name='use_default[url_key]']"/> + <element name="metaTitleInput" type="input" selector="input[name='product[meta_title]']"/> + <element name="metaKeywordsInput" type="textarea" selector="textarea[name='product[meta_keyword]']"/> + <element name="metaDescriptionInput" type="textarea" selector="textarea[name='product[meta_description]']"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml index 53af1d5bd6eb1..69d7297628d56 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml @@ -36,6 +36,7 @@ <element name="toggleWeight" type="checkbox" selector="#toggle_weight"/> <element name="toggleColor" type="checkbox" selector="#toggle_color"/> + <element name="name" type="input" selector="#name"/> <element name="description" type="input" selector="#description"/> </section> <section name="AdminUpdateAttributesWebsiteSection"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index 1cd64544d9636..ac7a15daf56aa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -24,7 +24,7 @@ <element name="mediaDescription" type="text" selector="img[alt='{{var1}}']" parameterized="true"/> <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> <element name="productImage" type="text" selector="img.product-image-photo"/> - <element name="productLink" type="text" selector="a.product-item-link"/> + <element name="productLink" type="text" selector="a.product-item-link" timeout="30"/> <element name="productLinkByHref" type="text" selector="a.product-item-link[href$='{{var1}}.html']" parameterized="true"/> <element name="productPrice" type="text" selector=".price-final_price"/> <element name="categoryImage" type="text" selector=".category-image"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml index 51b5a0242d976..4114d64eb39af 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml @@ -21,6 +21,8 @@ <element name="ProductTitleByName" type="button" selector="//main//li//a[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="ProductPriceByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//span[@class='price']" parameterized="true"/> + <element name="ProductCatalogRuleSpecialPriceTitleByName" type="text" selector="//div[descendant::*[contains(text(), '{{var1}}')]]//*[contains(@class, 'special-price')]" parameterized="true"/> + <element name="ProductCatalogRulePriceTitleByName" type="text" selector="//div[descendant::*[contains(text(), '{{var1}}')]]//*[contains(@class, 'price-label')]" parameterized="true"/> <element name="ProductImageByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//img[@class='product-image-photo']" parameterized="true"/> <element name="ProductImageBySrc" type="text" selector=".products-grid img[src*='{{pattern}}']" parameterized="true"/> <element name="ProductInfoByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//div[@class='product-item-info']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml index c6ea96715cf82..b2cd0f5f9af9f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -12,5 +12,6 @@ <element name="quantity" type="button" selector="span.counter-number"/> <element name="show" type="button" selector="a.showcart"/> <element name="goToCheckout" type="button" selector="#top-cart-btn-checkout" timeout="30"/> + <element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml index 98dc5e764fd77..5ee754904b702 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductActionSection"> <element name="quantity" type="input" selector="#qty"/> - <element name="addToCart" type="button" selector="#product-addtocart-button"/> + <element name="addToCart" type="button" selector="#product-addtocart-button" timeout="60"/> <element name="addToCartButtonTitleIsAdding" type="text" selector="//button/span[text()='Adding...']"/> <element name="addToCartButtonTitleIsAdded" type="text" selector="//button/span[text()='Added']"/> <element name="addToCartButtonTitleIsAddToCart" type="text" selector="//button/span[text()='Add to Cart']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 8393cee57996f..fd412d3c7dee1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -40,6 +40,7 @@ <!-- The 1st parameter is the nth custom option, the 2nd parameter is the nth value in the option --> <element name="nthCustomOptionInput" type="radio" selector="//*[@id='product-options-wrapper']/*[@class='fieldset']/*[contains(@class, 'field')][{{customOptionNum}}]//*[contains(@class, 'admin__field-option')][{{customOptionValueNum}}]//input" parameterized="true" /> + <element name="customOptionByTitle" type="text" selector="//span[text()='{{title}}']/ancestor::div[contains(@class, 'field ') and contains(@class, 'required')]" parameterized="true" timeout="30"/> <element name="productOptionRadioButtonsCheckbox" type="checkbox" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{var1}}')]/../div[@class='control']//input[@price='{{var2}}']" parameterized="true"/> <element name="productOptionDataMonth" type="date" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//legend[contains(.,'{{var1}}')]/../div[@class='control']//select[@data-calendar-role='month']" parameterized="true"/> <element name="productOptionDataDay" type="date" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//legend[contains(.,'{{var1}}')]/../div[@class='control']//select[@data-calendar-role='day']" parameterized="true"/> @@ -86,5 +87,21 @@ <element name="customSelectOptions" type="select" selector="#{{selectId}} option" parameterized="true"/> <element name="requiredCustomInput" type="text" selector="//div[contains(.,'{{customOptionTitle}}') and contains(@class, 'required') and .//input[@aria-required='true']]" parameterized="true"/> <element name="requiredCustomSelect" type="select" selector="//div[contains(.,'{{customOptionTitle}}') and contains(@class, 'required') and .//select[@aria-required='true']]" parameterized="true"/> + <element name="requiredCustomField" type="text" selector="//div[@class='field required']/label/span[contains(.,'{{optionTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomFile" type="text" selector="//div[@class='field file required']/label/span[contains(.,'{{OptionFileTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomTextArea" type="text" selector="//div[@class='field textarea required']/label/span[contains(.,'{{OptionAreaTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomDate" type="text" selector="//div[@class='field date required']//span[text()='{{OptionDateTitle}}']//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="customOptionField" type="input" selector="//input[contains(@class,'input-text product-custom-option')]"/> + <element name="customOptionTextArea" type="textarea" selector="//textarea[contains(@class,'product-custom-option')]"/> + <element name="customOptionDropDown" type="select" selector="//select[contains(@class,' required product-custom-option admin__control-select')]/option[contains(.,'{{option}}')]" parameterized="true"/> + <element name="customRadioOption" type="checkbox" selector="//div/input[@type='radio']/../label/span"/> + <element name="customOptionCheckBox" type="checkbox" selector="//div/input[@type='checkbox']/../label/span[contains(.,'{{option}}')]" parameterized="true"/> + <element name="customMultiSelectOption" type="select" selector="//select[contains(@class,'multiselect admin__control-multiselect required product-custom-option')]/option[contains(.,'{{option'}})]" parameterized="true"/> + <element name="customOptionMonth" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='month']" parameterized="true"/> + <element name="customOptionDay" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='day']" parameterized="true"/> + <element name="customOptionYear" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='year']" parameterized="true"/> + <element name="customOptionHour" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='hour']" parameterized="true"/> + <element name="customOptionMinute" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='minute']" parameterized="true"/> + <element name="customOptionDayPart" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='day_part']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml index ea10e12fb73f5..6ed359e35ab59 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -16,5 +16,6 @@ <element name="closeFullscreenImage" type="button" selector="//*[@data-gallery-role='gallery' and contains(@class, 'fullscreen')]//*[@data-gallery-role='fotorama__fullscreen-icon']" /> <element name="imageFile" type="text" selector="//*[@class='product media']//img[contains(@src, '{{filename}}')]" parameterized="true"/> <element name="productImageActive" type="text" selector=".product.media div[data-active=true] > img[src*='{{filename}}']" parameterized="true"/> + <element name="productImageInFotorama" type="file" selector=".fotorama__nav__shaft img[src*='{{imageName}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml index ee687fa62da93..7706c5f244bc9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml @@ -11,6 +11,9 @@ <section name="StorefrontProductMoreInformationSection"> <element name="moreInformation" type="button" selector="#tab-label-additional-title" timeout="30"/> <element name="moreInformationTextArea" type="textarea" selector="#additional"/> + <element name="moreInformationSpecs" type="text" selector="#product-attribute-specs-table"/> + <element name="customAttributeLabel" type="text" selector="//th[./following-sibling::td[@data-th='{{attributeCode}}']]" parameterized="true" /> + <element name="customAttributeValue" type="text" selector="//td[@data-th='{{attributeCode}}']" parameterized="true" /> <element name="attributeLabel" type="text" selector=".col.label"/> <element name="attributeValue" type="text" selector=".col.data"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml new file mode 100644 index 0000000000000..1f41ddefd0be6 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageDesignSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontProductPageDesignSection"> + <element name="layoutTwoColumnsLeft" type="block" selector=".page-layout-2columns-left"/> + <element name="layoutEmpty" type="block" selector=".page-layout-empty"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml index 8055ecfe00cde..78818dd37a5d4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml @@ -23,5 +23,7 @@ <element name="shipping" type="input" selector="span[data-th='Shipping']"/> <element name="orderTotal" type="input" selector=".grand.totals .amount .price"/> <element name="customOptionDropDown" type="select" selector="//*[@id='product-options-wrapper']//select[contains(@class, 'product-custom-option admin__control-select')]"/> + <element name="qtyInputWithProduct" type="input" selector="//tr//strong[contains(.,'{{productName}}')]/../../td[@class='col qty']//input" parameterized="true"/> + <element name="customOptionRadio" type="input" selector="//span[contains(text(),'{{customOption}}')]/../../input" parameterized="true"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml index 10d9342566040..9a0f5ad002725 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml @@ -11,19 +11,17 @@ <test name="AddOutOfStockProductToCompareListTest"> <annotations> <features value="Catalog"/> - <title value="Add out of stock product to compare list"/> - <description value="Add out of stock product to compare list"/> + <stories value="Product Comparison for products Out of Stock"/> + <title value="Add Product that is Out of Stock product to Product Comparison"/> + <description value="Customer should be able to add Product that is Out Of Stock to the Product Comparison"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-98644"/> <useCaseId value="MAGETWO-98522"/> <group value="Catalog"/> - <skip> - <issueId value="MC-15930"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 0" stepKey="displayOutOfStockNo"/> + <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockDisable.path}} {{CatalogInventoryOptionsShowOutOfStockDisable.value}}" stepKey="setConfigShowOutOfStockFalse"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <createData entity="SimpleSubCategory" stepKey="category"/> <createData entity="SimpleProduct4" stepKey="product"> @@ -31,7 +29,7 @@ </createData> </before> <after> - <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 0" stepKey="displayOutOfStockNo2"/> + <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockDisable.path}} {{CatalogInventoryOptionsShowOutOfStockDisable.value}}" stepKey="setConfigShowOutOfStockFalse"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <deleteData createDataKey="product" stepKey="deleteProduct"/> <deleteData createDataKey="category" stepKey="deleteCategory"/> @@ -39,29 +37,30 @@ </after> <!--Open product page--> <comment userInput="Open product page" stepKey="openProdPage"/> - <amOnPage url="{{StorefrontProductPage.url($$product.name$$)}}" stepKey="goToSimpleProductPage"/> + <amOnPage url="{{StorefrontProductPage.url($$product.custom_attributes[url_key]$$)}}" stepKey="goToSimpleProductPage"/> <waitForPageLoad stepKey="waitForSimpleProductPage"/> <!--'Add to compare' link is not available--> <comment userInput="'Add to compare' link is not available" stepKey="addToCompareLinkAvailability"/> <dontSeeElement selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="dontSeeAddToCompareLink"/> <!--Turn on 'out on stock' config--> <comment userInput="Turn on 'out of stock' config" stepKey="onOutOfStockConfig"/> - <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 1" stepKey="displayOutOfStockYes"/> + <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockEnable.path}} {{CatalogInventoryOptionsShowOutOfStockEnable.value}}" stepKey="setConfigShowOutOfStockTrue"/> <!--Clear cache and reindex--> <comment userInput="Clear cache and reindex" stepKey="cleanCache"/> <magentoCLI command="indexer:reindex" stepKey="reindex"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <!--Open product page--> <comment userInput="Open product page" stepKey="openProductPage"/> - <amOnPage url="{{StorefrontProductPage.url($$product.name$$)}}" stepKey="goToSimpleProductPage2"/> + <amOnPage url="{{StorefrontProductPage.url($$product.custom_attributes[url_key]$$)}}" stepKey="goToSimpleProductPage2"/> <waitForPageLoad stepKey="waitForSimpleProductPage2"/> <!--Click on 'Add to Compare' link--> + <waitForElementVisible selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="seeAddToCompareLink"/> <comment userInput="Click on 'Add to Compare' link" stepKey="clickOnAddToCompareLink"/> <click selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="clickOnAddToCompare"/> <waitForPageLoad stepKey="waitForProdAddToCmpList"/> <!--Assert success message--> <comment userInput="Assert success message" stepKey="assertSuccessMsg"/> - <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage"/> + <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage"/> <assertEquals expected='You added product $$product.name$$ to the comparison list.' expectedType="string" actual="($grabTextFromSuccessMessage)" stepKey="assertSuccessMessage"/> <!--See product in the comparison list--> <comment userInput="See product in the comparison list" stepKey="seeProductInComparisonList"/> @@ -69,7 +68,7 @@ <waitForPageLoad stepKey="waitForStorefrontProductComparePageLoad"/> <seeElement selector="{{StorefrontProductCompareMainSection.ProductLinkByName($product.name$)}}" stepKey="seeProductInCompareList"/> <!--Go to Category page and delete product from comparison list--> - <comment userInput="Go to Category page and delete prduct from comparison list" stepKey="deletProdFromCmpList"/> + <comment userInput="Go to Category page and delete product from comparison list" stepKey="deleteProdFromCmpList"/> <amOnPage url="{{StorefrontCategoryPage.url($$category.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> <click selector="{{StorefrontComparisonSidebarSection.ClearAll}}" stepKey="clickClearAll"/> @@ -83,7 +82,7 @@ <waitForPageLoad stepKey="waitProdAddingToCmpList"/> <!--Assert success message--> <comment userInput="Assert success message" stepKey="assertSuccessMsg2"/> - <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage2"/> + <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage2"/> <assertEquals expected='You added product $$product.name$$ to the comparison list.' expectedType="string" actual="($grabTextFromSuccessMessage)" stepKey="assertSuccessMessage2"/> <!--Check that product displays on add to compare widget--> <comment userInput="Check that product displays on add to compare widget" stepKey="checkProdNameOnWidget"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml new file mode 100644 index 0000000000000..2a4b119a5cabc --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddBundleProductToCartFromWishListPageTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAddBundleProductToCartFromWishListPageTest"> + <annotations> + <features value="Catalog"/> + <stories value="Add bundle product to Cart"/> + <title value="Add bundle product to Cart from Wish list page"/> + <description value="Add bundle product to Cart from Wish list page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17782"/> + <useCaseId value="MC-17387"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as Admin --> + <comment userInput="Login as Admin" stepKey="commentLoginAsAdmin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create customer on Storefront and bundle product --> + <comment userInput="Create customer on Storefront and bundle product" stepKey="commentCreateData"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="CustomerEntityOne" stepKey="createCustomerViaTheStorefront"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="_defaultProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create Attribute --> + <comment userInput="Create Attribute" stepKey="commentCreateAttribute"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="createProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createSimpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createSimpleProduct2"/> + </createData> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="goToProductEditPage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </before> + <after> + <!-- Delete created data --> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <deleteData createDataKey="createCustomerViaTheStorefront" stepKey="deleteCustomerViaTheStorefront"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct" /> + <!-- Log out --> + <comment userInput="Log out" stepKey="commentLogOut"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Login to the Storefront as created customer --> + <comment userInput="Login to the Storefront as created customer" stepKey="commentLoginAsCustomer"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomerViaTheStorefront$$"/> + </actionGroup> + <!-- Add product to Wish List --> + <comment userInput="Add product to Wish List" stepKey="commentAddProductToWishList"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="amOnBundleProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="StorefrontCustomerAddProductToWishlistActionGroup" stepKey="addToWishlistProduct"> + <argument name="productVar" value="$$createProduct$$"/> + </actionGroup> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName($$createProduct.name$$)}}" stepKey="moveMouseOverProduct" /> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName($$createProduct.name$$)}}" stepKey="clickAddToCart" /> + <waitForPageLoad stepKey="waitForProductBundlePage"/> + <!-- See error message --> + <comment userInput="See error message" stepKey="commentSeeErrorMessage"/> + <see userInput=" Please specify product option(s)." stepKey="seeErrorMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml index 03f3e93bb30ec..f3d3e653b260b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml @@ -16,6 +16,9 @@ <description value="Admin should be able to add image to WYSIWYG Editor on Product Page"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84375"/> + <skip> + <issueId value="MC-17232"/> + </skip> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> @@ -47,9 +50,9 @@ <conditionalClick selector="{{ProductDescriptionWYSIWYGToolbarSection.WysiwygArrow}}" dependentSelector="{{ProductDescriptionWYSIWYGToolbarSection.checkIfWysiwygArrowExpand}}" stepKey="clickWysiwygArrowIfClosed" visible="true"/> <waitForText userInput="{{ImageFolder.name}}" stepKey="waitForNewFolder1" /> <click userInput="{{ImageFolder.name}}" stepKey="clickOnCreatedFolder1" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading4" timeout="45"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoading4"/> <attachFile selector="{{ProductDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload1.value}}" stepKey="uploadImage1"/> - <waitForLoadingMaskToDisappear stepKey="waitForFileUpload1" timeout="30"/> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload1"/> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForUploadImage1" /> <seeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.imageSelected(ImageUpload1.value)}}" stepKey="seeImageSelected1" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" userInput="Delete Selected" stepKey="seeDeleteBtn1"/> @@ -60,7 +63,7 @@ <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="dontSeeImage1" /> <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn2" /> <attachFile selector="{{ProductDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload1.value}}" stepKey="uploadImage2"/> - <waitForLoadingMaskToDisappear stepKey="waitForFileUpload2" timeout="45"/> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload2"/> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForUploadImage2" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="clickInsertBtn1" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.ImageDescription}}" stepKey="waitForImageDescriptionButton1" /> @@ -72,12 +75,12 @@ <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.Browse}}" stepKey="clickBrowse2" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.CancelBtn}}" stepKey="waitForCancelButton2"/> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn2" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading13" timeout="30"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoading13"/> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn2" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading14" timeout="40"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoading14"/> <dontSeeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn3" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage3"/> - <waitForLoadingMaskToDisappear stepKey="waitForFileUpload3" timeout="45"/> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload3"/> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage3" /> <waitForElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" stepKey="waitForDeletebtn" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" userInput="Delete Selected" stepKey="seeDeleteBtn2"/> @@ -86,7 +89,7 @@ <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete2" /> <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn4" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage4"/> - <waitForLoadingMaskToDisappear stepKey="waitForFileUpload4" timeout="45"/> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload4"/> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage4" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="clickInsertBtn" /> <waitForLoadingMaskToDisappear stepKey="waitForLoading11" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml index e3f4d6cbdde0d..feb4fffd12f5d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml @@ -77,6 +77,7 @@ <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="1" stepKey="fillProductQuantity"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeSuccessSaveMessage"/> <seeElement selector="{{StorefrontMinicartSection.quantity(1)}}" stepKey="seeAddedProductQuantityInCart"/> <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml new file mode 100644 index 0000000000000..88c524eff387c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminBackorderAllowedAddProductToCartTest"> + <annotations> + <stories value="Manage products"/> + <title value="Add Product to Cart, Backorder Allowed"/> + <description value="Customer should be able to add products to cart when that products quantity is zero"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11063"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Create a product that is "In Stock" but has quantity zero --> + <createData entity="SimpleProductInStockQuantityZero" stepKey="createProduct"/> + + <!-- Configure Magento to show out of stock products and to allow backorders --> + <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockEnable.path}} {{CatalogInventoryOptionsShowOutOfStockEnable.value}}" stepKey="setConfigShowOutOfStockTrue"/> + <magentoCLI command="config:set {{CatalogInventoryItemOptionsBackordersEnable.path}} {{CatalogInventoryItemOptionsBackordersEnable.value}}" stepKey="setConfigAllowBackordersTrue"/> + </before> + + <after> + <!-- Set Magento back to default configuration --> + <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockDisable.path}} {{CatalogInventoryOptionsShowOutOfStockDisable.value}}" stepKey="setConfigShowOutOfStockFalse"/> + <magentoCLI command="config:set {{CatalogInventoryItemOptionsBackordersDisable.path}} {{CatalogInventoryItemOptionsBackordersDisable.value}}" stepKey="setConfigAllowBackordersFalse"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Go to the storefront and add the product to the cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="gotoAndAddProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Go to the cart page and verify we see the product --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="gotoCart"/> + <waitForPageLoad stepKey="waitForCartLoad"/> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createProduct.name$$"/> + <argument name="productSku" value="$$createProduct.sku$$"/> + <argument name="productPrice" value="$$createProduct.price$$"/> + <argument name="subtotal" value="$$createProduct.price$$" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml new file mode 100644 index 0000000000000..99adaeb522786 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCloneProductWithDuplicateUrlTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product"/> + <title value="Cloning a product with duplicate URL key"/> + <description value="Check product cloning with duplicate URL key"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-98992"/> + <useCaseId value="MAGETWO-98708"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create category and product--> + <comment userInput="Create category and product" stepKey="commentCreateCategoryAndProduct"/> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + </before> + <after> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetFiltersIfExist"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToProductEditPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <!--Save and duplicated the product once--> + <comment userInput="Save and duplicated the product once" stepKey="commentSaveAndDuplicateProduct"/> + <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductFormFirstTime"/> + <conditionalClick selector="{{AdminProductSEOSection.sectionHeader}}" dependentSelector="{{AdminProductSEOSection.urlKeyInput}}" visible="false" stepKey="openSEOSection"/> + <grabValueFrom selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="grabDuplicatedProductUrlKey"/> + <assertContains expected="$$createSimpleProduct.custom_attributes[url_key]$$" actual="$grabDuplicatedProductUrlKey" stepKey="assertDuplicatedProductUrlKey"/> + <assertContains expectedType="string" expected="-1" actual="$grabDuplicatedProductUrlKey" stepKey="assertDuplicatedProductUrlKey1"/> + <!--Add duplicated product to the simple product--> + <comment userInput="Add duplicated product to the simple product" stepKey="commentAddProduct"/> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad1"/> + <actionGroup ref="addCrossSellProductBySku" stepKey="addCrossSellProduct"> + <argument name="sku" value="$$createSimpleProduct.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct"> + <argument name="sku" value="$$createSimpleProduct.sku$$"/> + </actionGroup> + <actionGroup ref="addUpSellProductBySku" stepKey="addUpSellProduct"> + <argument name="sku" value="$$createSimpleProduct.sku$$"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="clickSaveProduct"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.sectionHeader}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.AddRelatedProductsButton}}" visible="false" stepKey="openProductRUSSection"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('related')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeRelatedProduct"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('upsell')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeUpSellProduct"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('crosssell')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeCrossSellProduct"/> + <!--Save and duplicated the product second time--> + <comment userInput="Save and duplicated the product second time" stepKey="commentSaveAndDuplicateProduct1"/> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToProductEditPage1"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad2"/> + <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductFormSecondTime"/> + <conditionalClick selector="{{AdminProductSEOSection.sectionHeader}}" dependentSelector="{{AdminProductSEOSection.urlKeyInput}}" visible="false" stepKey="openProductSEOSection"/> + <waitForElementVisible selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="waitForUrlKeyField"/> + <grabValueFrom selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="grabSecondDuplicatedProductUrlKey"/> + <assertContains expected="$$createSimpleProduct.custom_attributes[url_key]$$" actual="$grabSecondDuplicatedProductUrlKey" stepKey="assertSecondDuplicatedProductUrlKey"/> + <assertContains expectedType="string" expected="-2" actual="$grabSecondDuplicatedProductUrlKey" stepKey="assertSecondDuplicatedProductUrlKey1"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.sectionHeader}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.AddRelatedProductsButton}}" visible="false" stepKey="openProductRUSSection1"/> + <waitForElementVisible selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('related')}}" stepKey="waitForSelectedProductSku"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('related')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeRelatedProductForDuplicated"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('upsell')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeUpSellProductForDuplicated"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedProductSku('crosssell')}}" userInput="$$createSimpleProduct.sku$$-1" stepKey="seeCrossSellProductForDuplicated"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml new file mode 100644 index 0000000000000..e3b0ae3746387 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateAndEditSimpleProductSettingsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create/Edit simple product in Admin"/> + <title value="Admin should be able to set/edit other product information when creating/editing a simple product"/> + <description value="Admin should be able to set/edit product information when creating/editing a simple product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3241"/> + <group value="Catalog"/> + </annotations> + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create related products --> + <createData entity="SimpleProduct2" stepKey="createFirstRelatedProduct"/> + <createData entity="SimpleProduct2" stepKey="createSecondRelatedProduct"/> + <createData entity="SimpleProduct2" stepKey="createThirdRelatedProduct"/> + </before> + <after> + <!-- Delete related products --> + <deleteData createDataKey="createFirstRelatedProduct" stepKey="deleteFirstRelatedProduct"/> + <deleteData createDataKey="createSecondRelatedProduct" stepKey="deleteSecondRelatedProduct"/> + <deleteData createDataKey="createThirdRelatedProduct" stepKey="deleteThirdRelatedProduct"/> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create new simple product --> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createSimpleProduct"/> + + <!-- Fill all main fields --> + <actionGroup ref="fillMainProductForm" stepKey="fillAllNecessaryFields"/> + + <!-- Add two related products --> + <actionGroup ref="addRelatedProductBySku" stepKey="addFirstRelatedProduct"> + <argument name="sku" value="$$createFirstRelatedProduct.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addSecondRelatedProduct"> + <argument name="sku" value="$$createSecondRelatedProduct.sku$$"/> + </actionGroup> + + <!-- Set Design settings for the product --> + <actionGroup ref="AdminSetProductDesignSettingsActionGroup" stepKey="setProductDesignSettings"/> + + <!-- Set Gift Options settings for the product --> + <actionGroup ref="AdminSwitchProductGiftMessageStatusActionGroup" stepKey="enableGiftMessageSettings"> + <argument name="status" value="1"/> + </actionGroup> + + <!-- Save product form --> + <actionGroup ref="saveProductForm" stepKey="clickSaveButton"/> + + <!-- Open product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage"> + <argument name="productUrl" value="{{_defaultProduct.name}}"/> + </actionGroup> + + <!-- Assert related products at the storefront --> + <seeElement selector="{{StorefrontProductRelatedProductsSection.relatedProductName($$createFirstRelatedProduct.name$$)}}" stepKey="seeFirstRelatedProductInStorefront"/> + <seeElement selector="{{StorefrontProductRelatedProductsSection.relatedProductName($$createSecondRelatedProduct.name$$)}}" stepKey="seeSecondRelatedProductInStorefront"/> + + <!-- Assert product design settings "left bar is present at product page with 2 columns" --> + <seeElement selector="{{StorefrontProductPageDesignSection.layoutTwoColumnsLeft}}" stepKey="seeDesignChanges"/> + + <!-- Assert Gift Option product settings is present --> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="_defaultProduct"/> + <argument name="productCount" value="1"/> + </actionGroup> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openShoppingCart"/> + <actionGroup ref="StorefrontAssertGiftMessageFieldsActionGroup" stepKey="assertGiftMessageFieldsArePresent"/> + + <!-- Open created product --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + + <!-- Edit product Search Engine Optimization settings --> + <actionGroup ref="AdminChangeProductSEOSettingsActionGroup" stepKey="editProductSEOSettings"> + <argument name="productName" value="SimpleProduct.name"/> + </actionGroup> + + <!-- Edit related products --> + <actionGroup ref="addRelatedProductBySku" stepKey="addThirdRelatedProduct"> + <argument name="sku" value="$$createThirdRelatedProduct.sku$$"/> + </actionGroup> + <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.removeRelatedProduct($$createFirstRelatedProduct.sku$$)}}" stepKey="removeFirstRelatedProduct"/> + + <!-- Edit Design settings for the product --> + <actionGroup ref="AdminSetProductDesignSettingsActionGroup" stepKey="editProductDesignSettings"> + <argument name="designSettings" value="simpleLumaDesign"/> + </actionGroup> + + <!-- Edit Gift Option product settings --> + <actionGroup ref="AdminSwitchProductGiftMessageStatusActionGroup" stepKey="disableGiftMessageSettings"/> + + <!-- Save product form --> + <actionGroup ref="saveProductForm" stepKey="clickSaveProduct"/> + + <!-- Verify Url Key after changing --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="{{SimpleProduct.name}}"/> + </actionGroup> + + <!-- Assert related products at the storefront --> + <seeElement selector="{{StorefrontProductRelatedProductsSection.relatedProductName($$createSecondRelatedProduct.name$$)}}" stepKey="seeSecondRelatedProduct"/> + <seeElement selector="{{StorefrontProductRelatedProductsSection.relatedProductName($$createThirdRelatedProduct.name$$)}}" stepKey="seeThirdRelatedProduct"/> + + <!-- Assert product design settings "Layout empty" --> + <seeElement selector="{{StorefrontProductPageDesignSection.layoutEmpty}}" stepKey="seeNewDesignChanges"/> + + <!-- Assert Gift Option product settings --> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <dontSeeElement selector="{{StorefrontProductCartGiftOptionSection.giftOptions}}" stepKey="dontSeeGiftOptionBtn"/> + + <!-- Delete created simple product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml index 8806612c0f5de..15171fe3713c3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml @@ -96,7 +96,7 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> <see selector="{{AdminCategoryBasicFieldSection.FieldError('uid')}}" userInput="This is a required field." stepKey="seeErrorMessage"/> <!-- Verify that the Layered navigation price step field has the required indicator --> - <comment userInput="Check if Layered navigation price field has required indictor icon" stepKey="comment" /> + <comment userInput="Check if Layered navigation price field has required indicator icon" stepKey="comment" /> <executeJS function="{{CategoryDisplaySettingsSection.RequiredFieldIndicator('filter_price_range')}}" stepKey="getRequiredFieldIndicator"/> <assertEquals expected='"*"' expectedType="string" actualType="variable" actual="getRequiredFieldIndicator" message="pass" stepKey="assertRequiredFieldIndicator1"/> </test> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index 1f558568e9248..4e096b7ebb142 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -34,6 +34,12 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> + <!-- Delete product attribute --> + <deleteData createDataKey="attribute" stepKey="deleteProductAttribute"/> + + <!-- Delete product attribute set --> + <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> + <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml index 282331924bca3..02615ca5dd254 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml @@ -11,6 +11,7 @@ <test name="AdminCreateNewAttributeFromProductTest"> <annotations> <features value="Catalog"/> + <stories value="Product attributes"/> <title value="Check that New Attribute from Product is create"/> <description value="Check that New Attribute from Product is create"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml new file mode 100644 index 0000000000000..3219bca233bee --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateNewGroupForAttributeSetTest"> + <annotations> + <stories value="Edit attribute set"/> + <title value="Admin should be able to create new group in an Attribute Set"/> + <description value="The test verifies creating a new group in an attribute set and a validation message in case of empty group name"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-170"/> + <group value="Catalog"/> + </annotations> + <before> + <!-- Create a custom attribute set and custom product attribute --> + <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + + <!-- Login to Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Navigate to Stores > Attributes > Attribute Set --> + <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSetPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Search and open Attribute Set from preconditions --> + <actionGroup ref="goToAttributeSetByName" stepKey="searchAttribute"> + <argument name="name" value="$$createAttributeSet.attribute_set_name$$"/> + </actionGroup> + + <!-- Click 'Add New': Show 'New Group' Modal --> + <click selector="{{AdminProductAttributeSetEditSection.AddNewGroup}}" stepKey="clickAddNew"/> + <waitForAjaxLoad stepKey="waitForAjax"/> + + <!-- Fill 'name' for new group and click 'Ok': Name = <empty> --> + <fillField userInput="" selector="{{AdminProductAttributeSetEditSection.newGroupName}}" stepKey="fillName"/> + <click selector="{{AdminProductAttributeSetEditSection.buttonOk}}" stepKey="clickOk"/> + + <!-- Error message 'This is a required field.' is displayed --> + <see userInput="This is a required field." selector="{{AdminProductAttributeSetEditSection.errorLabel}}" stepKey="seeErrorMessage"/> + + <!-- Fill 'name' for new group and click 'Ok': Name = Custom group --> + <fillField userInput="{{customGroup.name}}" selector="{{AdminProductAttributeSetEditSection.newGroupName}}" stepKey="fillCustomGroupName"/> + <click selector="{{AdminProductAttributeSetEditSection.buttonOk}}" stepKey="clickButtonOk"/> + + <!-- Group is created and displayed in 'Groups' block --> + <seeElement selector="{{AdminProductAttributeSetEditSection.attributeGroup(customGroup.name)}}" stepKey="assertCustomGroup"/> + + <!-- Move custom Product Attribute to new 'Custom group' Group --> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminProductAttributeSetEditSection.attributeGroupExtender(customGroup.name)}}" stepKey="click"/> + <waitForPageLoad stepKey="waitForPageLoadAfterClick"/> + <dragAndDrop selector1="{{AdminProductAttributeSetEditSection.unassignedAttribute($$createConfigProductAttribute.attribute_code$$)}}" selector2="{{AdminProductAttributeSetEditSection.attributeGroupExtender(customGroup.name)}}" stepKey="moveAttribute"/> + <waitForPageLoad stepKey="waitForDragAndDrop"/> + + <!-- Attribute is displayed in the new group --> + <see userInput="$$createConfigProductAttribute.attribute_code$$" selector="{{AdminProductAttributeSetEditSection.groupTree}}" stepKey="seeAttribute"/> + + <!-- Click 'Save' --> + <actionGroup ref="SaveAttributeSet" stepKey="saveAttribute"/> + + <actionGroup ref="goToAttributeSetByName" stepKey="backTohAttributeSet"> + <argument name="name" value="$$createAttributeSet.attribute_set_name$$"/> + </actionGroup> + + <!-- Create another group: Name = Empty group --> + <click selector="{{AdminProductAttributeSetEditSection.AddNewGroup}}" stepKey="clickAddEmptyGroup"/> + <waitForAjaxLoad stepKey="waitForLoad"/> + + <fillField userInput="{{emptyGroup.name}}" selector="{{AdminProductAttributeSetEditSection.newGroupName}}" stepKey="fillGroupName"/> + <click selector="{{AdminProductAttributeSetEditSection.buttonOk}}" stepKey="clickOnOk"/> + <waitForPageLoad stepKey="waitForNewGroup"/> + + <!-- Empty group is created. No attributes are assigned to it. --> + <seeElement selector="{{AdminProductAttributeSetEditSection.attributeGroup(emptyGroup.name)}}" stepKey="assertEmptyGroup"/> + <dontSeeElement selector="{{AdminProductAttributeSetEditSection.attributesInGroup(emptyGroup.name)}}" stepKey="seeNoAttributes"/> + + <!-- Navigate to Catalog > Products --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Start to create a new simple product with the custom attribute set from the preconditions --> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddProduct"/> + <waitForPageLoad stepKey="waitForNewProductPage"/> + + <actionGroup ref="AdminProductPageSelectAttributeSet" stepKey="selectAttribute"> + <argument name="attributeSetName" value="$$createAttributeSet.attribute_set_name$$"/> + </actionGroup> + + <!-- New Section 'Custom group' is present in form. The section contains the attribute from preconditions --> + <seeElement selector="{{AdminProductAttributeSection.attributeGroupByName(customGroup.name)}}" stepKey="seeSectionCustomGroup"/> + <click selector="{{AdminProductAttributeSection.attributeGroupByName(customGroup.name)}}" stepKey="openCustomGroupSection"/> + <waitForAjaxLoad stepKey="waitForOpenSection"/> + <scrollTo selector="{{AdminProductFormSection.footerBlock}}" stepKey="scrollToFooter"/> + <seeElement selector="{{AdminProductAttributeSection.attributeByGroupAndName(customGroup.name)}}" stepKey="seeAttributePresent"/> + + <!-- Empty section is absent in Product Form --> + <dontSeeElement selector="{{AdminProductAttributeSection.attributeGroupByName(emptyGroup.name)}}" stepKey="dontSeeEmptyGroup"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateTextEditorProductAttributeTest.xml new file mode 100644 index 0000000000000..fc7482c353136 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateTextEditorProductAttributeTest.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTextEditorProductAttributeTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create product Attribute"/> + <title value="Admin create text editor product attribute test"/> + <description value="Create text editor product attribute with TinyMCE4 enabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-6338"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Enable WYSIWYG editor --> + <magentoCLI command="config:set {{EnableWYSIWYG.path}} {{EnableWYSIWYG.value}}" stepKey="enableWYSIWYG"/> + + <!-- Enable TinyMCE 4 --> + <magentoCLI command="config:set {{EnableTinyMCE4.path}} {{EnableTinyMCE4.value}}" stepKey="enableTinyMCE4"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete attribute --> + <actionGroup ref="deleteProductAttribute" stepKey="deleteAttribute"> + <argument name="ProductAttribute" value="productTextEditorAttribute"/> + </actionGroup> + + <!-- Delete product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to Stores > Product, click "Add New Attribute" --> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="openProductAttributePage"/> + <click selector="{{AdminProductAttributeGridSection.createNewAttributeBtn}}" stepKey="createNewAttribute"/> + + <!-- Input value for Default Label. Verify dropdown of "Catalog Input Type for Store Owner" --> + <actionGroup ref="AdminFillProductAttributePropertiesActionGroup" stepKey="fillAttributeProperties"> + <argument name="attributeName" value="{{productTextEditorAttribute.attribute_code}}"/> + <argument name="attributeType" value="{{productTextEditorAttribute.frontend_input}}"/> + </actionGroup> + + <!-- Input value for "Catalog Input Type for Store Owner" --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{productAttributeWysiwyg.frontend_input}}" stepKey="updateInputType"/> + + <!-- Click on "Storefront Properties" tab on left menu --> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="clickStorefrontPropertiesTab"/> + <dontSeeElement selector="{{StorefrontPropertiesSection.EnableWYSIWYG}}" stepKey="dontSeeWYSIWYGEnableField"/> + + <!-- Selection for "Visible on Catalog Pages on Storefront" --> + <selectOption selector="{{StorefrontPropertiesSection.visibleOnCatalogPagesOnStorefront}}" userInput="Yes" stepKey="enableVisibleOnStorefront"/> + <scrollToTopOfPage stepKey="scrollToPageTop"/> + + <!-- Go back to "Properties" tab on left menu --> + <click selector="{{AttributePropertiesSection.propertiesTab}}" stepKey="clickPropertiesTab"/> + + <!-- Updated value for "Catalog Input Type for Store Owner" --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{productTextEditorAttribute.frontend_input}}" stepKey="returnInputType"/> + + <!-- Save Product Attribute --> + <actionGroup ref="saveProductAttribute" stepKey="saveAttribute"/> + + <!-- Go to Store > Attribute Set --> + <actionGroup ref="AdminOpenAttributeSetGridPageActionGroup" stepKey="openAttributeSetPage"/> + + <!-- From grid, click on attribute set Default --> + <actionGroup ref="AdminOpenAttributeSetByNameActionGroup" stepKey="openDefaultAttributeSet"/> + + <!-- Add Product Attribute to Default attribute by dragging and dropping this to the 'Project Details' folder. Then Save. --> + <actionGroup ref="AssignAttributeToGroup" stepKey="assignAttributeToGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="{{productTextEditorAttribute.attribute_code}}"/> + </actionGroup> + <actionGroup ref="SaveAttributeSet" stepKey="saveAttributeSet"/> + + <!-- Go Catalog > Product to create new product page --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="goToProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + + <!-- On product page, select Attribute Set: "Default" --> + <actionGroup ref="AdminProductPageSelectAttributeSet" stepKey="selectAttributeSet"> + <argument name="attributeSetName" value="Default"/> + </actionGroup> + + <!-- Created product attribute appear on product form --> + <seeElement selector="{{AdminProductFormSection.attributeLabelByText(productTextEditorAttribute.attribute_code)}}" stepKey="seeAttributeLabelInProductForm"/> + + <!-- TinyMCE 4 is displayed in WYSIWYG content area --> + <seeElement selector="{{TinyMCESection.TinyMCE4}}" stepKey="seeTinyMCE4"/> + + <!-- Verify toolbar menu --> + <actionGroup ref="VerifyTinyMCEActionGroup" stepKey="verifyToolbarMenu"/> + + <!-- Click Show/Hide button and see Insert Image button --> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{ProductAttributeWYSIWYGSection.showHideBtn(productTextEditorAttribute.attribute_code)}}" stepKey="clickShowHideBtn"/> + <waitForElementVisible selector="{{TinyMCESection.InsertImageBtn}}" stepKey="waitForInsertImageBtn"/> + + <!-- Add content into attribute --> + <fillField selector="{{ProductDescriptionWysiwygSection.attributeEditArea(productTextEditorAttribute.attribute_code)}}" userInput="This content from product page" stepKey="setContent"/> + + <!-- Fill up all required fields for product form --> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"/> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!-- Assert product attribute on Storefront --> + <actionGroup ref="OpenStorefrontProductPageByProductNameActionGroup" stepKey="openProductPage"/> + <scrollTo stepKey="scrollToMoreInformation" selector="{{StorefrontProductMoreInformationSection.moreInformationSpecs}}" /> + <actionGroup ref="AssertStorefrontCustomProductAttributeActionGroup" stepKey="checkAttributeInMoreInformationTab"> + <argument name="attributeLabel" value="{{productTextEditorAttribute.attribute_code}}"/> + <argument name="attributeValue" value="This content from product page"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml index 26ad7a46a73d7..0b929eaddc96e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml @@ -19,11 +19,15 @@ <group value="mtf_migrated"/> </annotations> <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteVirtualProduct"> + <argument name="sku" value="{{virtualProductOutOfStock.sku}}"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFilter"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml index 17769c79677f7..58737dd509743 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml @@ -19,11 +19,15 @@ <group value="mtf_migrated"/> </annotations> <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteVirtualProduct"> + <argument name="sku" value="{{virtualProductCustomImportOptions.sku}}"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="resetOrderFilter"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml index 4d28ccbd44d2c..cbe2f40e0dd25 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml @@ -10,9 +10,10 @@ <test name="AdminDeleteAttributeSetTest"> <annotations> <features value="Catalog"/> + <stories value="Attribute sets"/> <title value="Delete Attribute Set"/> <description value="Admin should be able to delete an attribute set"/> - <testCaseId value="MC-4413"/> + <testCaseId value="MC-10889"/> <severity value="CRITICAL"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml index 54b83e034fb11..d0036a2adea5a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml @@ -10,6 +10,7 @@ <test name="DeleteProductAttributeTest"> <annotations> <features value="Catalog"/> + <stories value="Product attributes"/> <title value="Delete Product Attribute"/> <description value="Admin should able to delete a product attribute"/> <testCaseId value="MC-10887"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index e914b8c96d03e..53040993beb8f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -12,10 +12,10 @@ <features value="Catalog"/> <stories value="MAGETWO-51484-Input type configuration for custom Product Attributes"/> <group value="Catalog"/> - <title value="Admin should be able to switch between two versions of TinyMCE"/> - <description value="Admin should be able to switch between two versions of TinyMCE"/> + <title value="Admin are able to change Input Type of Text Editor product attribute"/> + <description value="Admin are able to change Input Type of Text Editor product attribute"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-85745"/> + <testCaseId value="MC-6215"/> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="loginGetFromGeneralFile"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml index b24ed7f9c9a81..8af7701533860 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -11,12 +11,16 @@ <test name="AdminGridPageNumberAfterSaveAndCloseActionTest"> <annotations> <features value="Catalog"/> + <stories value="Catalog grid"/> <title value="Checking Catalog grid page number after Save and Close action"/> <description value="Checking Catalog grid page number after Save and Close action"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-96164"/> <useCaseId value="MAGETWO-96127"/> <group value="Catalog"/> + <skip> + <issueId value="MC-17332"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -24,9 +28,7 @@ <comment userInput="Clear product grid" stepKey="commentClearProductGrid"/> <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> <waitForPageLoad stepKey="waitForProductIndexPage"/> - <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" - dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> - <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridToDefaultView"/> <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteProductIfTheyExist"/> <createData stepKey="category1" entity="SimpleSubCategory"/> <createData stepKey="product1" entity="SimpleProduct"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml index 79ff7bcade77b..b1f00a2f51a95 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml @@ -11,12 +11,16 @@ <test name="AdminImportCustomizableOptionToProductWithSKUTest"> <annotations> <features value="Catalog"/> + <stories value="Customizable options"/> <title value="Import customizable options to a product with existing SKU"/> <description value="Import customizable options to a product with existing SKU"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-98211"/> <useCaseId value="MAGETWO-70232"/> <group value="catalog"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -48,11 +52,9 @@ <waitForPageLoad stepKey="waitForProductEditPageLoad"/> <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption1"> <argument name="option" value="ProductOptionField"/> - <argument name="optionIndex" value="0"/> </actionGroup> <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption2"> <argument name="option" value="ProductOptionField2"/> - <argument name="optionIndex" value="1"/> </actionGroup> <actionGroup ref="saveProductForm" stepKey="saveProduct"/> <!--Change second product sku to first product sku--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml index 8a44c8093ca5e..87e0bf3d2e9a0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -18,6 +18,9 @@ <testCaseId value="MC-56"/> <group value="Catalog"/> <group value="Product Attributes"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml new file mode 100644 index 0000000000000..da985fc2ce34d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml @@ -0,0 +1,227 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMoveProductBetweenCategoriesTest"> + <annotations> + <stories value="Move Product"/> + <title value="Move Product between Categories (Cron is ON, 'Update by Schedule' Mode)"/> + <description value="Verifies correctness of showing data (products, categories) on Storefront after moving an anchored category in terms of products/categories association"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11296"/> + <group value="catalog"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + <createData entity="_defaultCategory" stepKey="createAnchoredCategory1"/> + <createData entity="_defaultCategory" stepKey="createSecondCategory"/> + + <!-- Switch "Category Product" and "Product Category" indexers to "Update by Schedule" mode --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="onIndexManagement"/> + <waitForPageLoad stepKey="waitForManagementPage"/> + + <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchCategoryProduct"> + <argument name="indexerValue" value="catalog_category_product"/> + </actionGroup> + <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchProductCategory"> + <argument name="indexerValue" value="catalog_product_category"/> + </actionGroup> + </before> + + <after> + <!-- Switch "Category Product" and "Product Category" indexers to "Update by Save" mode --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="onIndexManagement"/> + <waitForPageLoad stepKey="waitForManagementPage"/> + + <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchCategoryProduct"> + <argument name="indexerValue" value="catalog_category_product"/> + <argument name="action" value="Update on Save"/> + </actionGroup> + <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchProductCategory"> + <argument name="indexerValue" value="catalog_product_category"/> + <argument name="action" value="Update on Save"/> + </actionGroup> + + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createSecondCategory" stepKey="deleteSecondCategory"/> + <deleteData createDataKey="createAnchoredCategory1" stepKey="deleteAnchoredCategory1"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create the anchored category <Cat1_anchored> --> + <actionGroup ref="AdminAnchorCategoryActionGroup" stepKey="anchorCategory"> + <argument name="categoryName" value="$$createAnchoredCategory1.name$$"/> + </actionGroup> + + <!-- Create subcategory <Sub1> of the anchored category --> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="addSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory1"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSaveSuccessMessage"/> + + <!-- Assign <product1> to the <Sub1> --> + <amOnPage url="{{AdminProductEditPage.url($$simpleProduct.id$$)}}" stepKey="goToProduct"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="activateDropDownCategory"/> + <fillField userInput="{{SimpleSubCategory.name}}" selector="{{AdminProductFormSection.searchCategory}}" stepKey="fillSearch"/> + <waitForPageLoad stepKey="waitForSubCategory"/> + <click selector="{{AdminProductFormSection.selectCategory(SimpleSubCategory.name)}}" stepKey="selectSub1Category"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickDone"/> + <waitForPageLoad stepKey="waitForApplyCategory"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSavingChanges"/> + + <!-- Enable `Use Categories Path for Product URLs` on Stores -> Configuration -> Catalog -> Catalog -> Search Engine Optimization --> + <amOnPage url="{{AdminCatalogSearchConfigurationPage.url}}" stepKey="onConfigPage"/> + <waitForPageLoad stepKey="waitForLoading"/> + <conditionalClick selector="{{AdminCatalogSearchEngineConfigurationSection.searchEngineOptimization}}" dependentSelector="{{AdminCatalogSearchEngineConfigurationSection.openedEngineOptimization}}" visible="false" stepKey="clickEngineOptimization"/> + <uncheckOption selector="{{AdminCatalogSearchEngineConfigurationSection.systemValueUseCategoriesPath}}" stepKey="uncheckDefault"/> + <selectOption userInput="Yes" selector="{{AdminCatalogSearchEngineConfigurationSection.selectUseCategoriesPatForProductUrls}}" stepKey="selectYes"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForSaving"/> + <see selector="{{AdminIndexManagementSection.successMessage}}" userInput="You saved the configuration." stepKey="seeMessage"/> + + <!-- Navigate to the Catalog > Products --> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="onCatalogProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!-- Click on <product1>: Product page opens--> + <actionGroup ref="filterProductGridByName" stepKey="filterProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + <click selector="{{AdminProductGridSection.productGridNameProduct($$simpleProduct.name$$)}}" stepKey="clickProduct1"/> + <waitForPageLoad stepKey="waitForProductLoad"/> + + <!-- Clear "Categories" field and assign the product to <Cat2> and save the product --> + <grabTextFrom selector="{{AdminProductFormSection.currentCategory}}" stepKey="grabNameSubCategory"/> + <click selector="{{AdminProductFormSection.unselectCategories(SimpleSubCategory.name)}}" stepKey="removeCategory"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="openDropDown"/> + <checkOption selector="{{AdminProductFormSection.selectCategory($$createSecondCategory.name$$)}}" stepKey="selectCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="pressButtonDone"/> + <waitForPageLoad stepKey="waitForApplyCategory2"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="pushButtonSave"/> + <waitForPageLoad stepKey="waitForSavingProduct"/> + + <!--Product is saved --> + <see userInput="You saved the product." selector="{{CatalogProductsSection.messageSuccessSavedProduct}}" stepKey="seeSuccessMessage"/> + + <!-- Run cron --> + <magentoCLI command="cron:run" stepKey="runCron"/> + + <!-- Clear invalidated cache on System>Tools>Cache Management page --> + <amOnPage url="{{AdminCacheManagementPage.url}}" stepKey="onCachePage"/> + <waitForPageLoad stepKey="waitForCacheManagementPage"/> + + <checkOption selector="{{AdminCacheManagementSection.configurationCheckbox}}" stepKey="checkConfigCache"/> + <checkOption selector="{{AdminCacheManagementSection.pageCacheCheckbox}}" stepKey="checkPageCache"/> + + <selectOption userInput="Refresh" selector="{{AdminCacheManagementSection.massActionSelect}}" stepKey="selectRefresh"/> + <waitForElementVisible selector="{{AdminCacheManagementSection.massActionSubmit}}" stepKey="waitSubmitButton"/> + <click selector="{{AdminCacheManagementSection.massActionSubmit}}" stepKey="clickSubmit"/> + <waitForPageLoad stepKey="waitForRefresh"/> + + <see userInput="2 cache type(s) refreshed." stepKey="seeCacheRefreshedMessage"/> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Open frontend --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="onFrontend"/> + <waitForPageLoad stepKey="waitForStorefrontPageLoad"/> + + <!-- Open <Cat2> from navigation menu --> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategory.name$$)}}" stepKey="openCat2"/> + <waitForPageLoad stepKey="waitForCategory2Page"/> + + <!-- # <Cat 2> should open # <product1> should be present on the page --> + <see userInput="$$createSecondCategory.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryName"/> + <see userInput="$$simpleProduct.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProduct"/> + + <!-- Open <product1> --> + <click selector="{{StorefrontCategoryMainSection.productLinkByHref($$simpleProduct.urlKey$$)}}" stepKey="openProduct"/> + <waitForPageLoad stepKey="waitForProductPageLoading"/> + + <!-- # Product page should open successfully # Breadcrumb for product should be like <Cat 2> --> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$simpleProduct.name$$" stepKey="seeProductName"/> + <see userInput="$$createSecondCategory.name$$" selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="seeCategoryInBreadcrumbs"/> + + <!-- Open <Cat1_anchored> category --> + <click selector="{{StorefrontNavigationSection.topCategory($$createAnchoredCategory1.name$$)}}" stepKey="clickCat1"/> + <waitForPageLoad stepKey="waitForCategory1PageLoad"/> + + <!-- # Category should open successfully # <product1> should be absent on the page --> + <see userInput="$$createAnchoredCategory1.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategory1Name"/> + <see userInput="We can't find products matching the selection." stepKey="seeEmptyNotice"/> + <dontSee userInput="$$simpleProduct.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeProduct"/> + + <!-- Log in to the backend: Admin user is logged in--> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAdmin"/> + + <!-- Navigate to the Catalog > Products: Navigate to the Catalog>Products --> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductsPage"/> + + <!-- Click on <product1> --> + <actionGroup ref="filterAndSelectProduct" stepKey="openSimpleProduct"> + <argument name="productSku" value="$$simpleProduct.sku$$"/> + </actionGroup> + + <!-- Clear "Categories" field and assign the product to <Sub1> and save the product --> + <click selector="{{AdminProductFormSection.unselectCategories($$createSecondCategory.name$$)}}" stepKey="clearCategory"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="activateDropDown"/> + <fillField userInput="{$grabNameSubCategory}" selector="{{AdminProductFormSection.searchCategory}}" stepKey="fillSearchField"/> + <waitForPageLoad stepKey="waitForSearchSubCategory"/> + <click selector="{{AdminProductFormSection.selectCategory({$grabNameSubCategory})}}" stepKey="selectSubCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickButtonDone"/> + <waitForPageLoad stepKey="waitForCategoryApply"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickButtonSave"/> + <waitForPageLoad stepKey="waitForSaveChanges"/> + + <!-- Product is saved successfully --> + <see userInput="You saved the product." selector="{{CatalogProductsSection.messageSuccessSavedProduct}}" stepKey="seeSaveMessage"/> + + <!-- Run cron --> + <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Open frontend --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="onFrontendPage"/> + <waitForPageLoad stepKey="waitForFrontPageLoad"/> + + <!-- Open <Cat2> from navigation menu --> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategory.name$$)}}" stepKey="openSecondCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryPage"/> + + <!-- # <Cat 2> should open # <product1> should be absent on the page --> + <see userInput="$$createSecondCategory.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeSecondCategory1Name"/> + <dontSee userInput="$$simpleProduct.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeSimpleProduct"/> + + <!-- Click on <Cat1_anchored> category --> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createAnchoredCategory1.name$$)}}" stepKey="clickAnchoredCategory"/> + <waitForPageLoad stepKey="waitForAnchoredCategoryPage"/> + + <!-- # Category should open successfully # <product1> should be present on the page --> + <see userInput="$$createAnchoredCategory1.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="see1CategoryName"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="$$simpleProduct.name$$" stepKey="seeProductNameOnCategory1Page"/> + + <!-- Breadcrumb for product should be like <Cat1_anchored>/<product> (if you clicks from anchor category) --> + <see userInput="$$createAnchoredCategory1.name$$" selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="seeCat1inBreadcrumbs"/> + <dontSee userInput="{$grabNameSubCategory}" selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="dontSeeSubCategoryInBreadCrumbs"/> + + <!-- <Cat1_anchored>/<Sub1>/<product> (if you clicks from Sub1 category) --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createAnchoredCategory1.name$$)}}" stepKey="hoverCategory1"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName({$grabNameSubCategory})}}" stepKey="clickSubCat"/> + <waitForPageLoad stepKey="waitForSubCategoryPageLoad"/> + + <see userInput="{$grabNameSubCategory}" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeSubCategoryName"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="$$simpleProduct.name$$" stepKey="seeProductNameOnSubCategoryPage"/> + + <see userInput="{$grabNameSubCategory}" selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="seeSubCategoryInBreadcrumbs"/> + <see userInput="$$createAnchoredCategory1.name$$" selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="seeCat1InBreadcrumbs"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml new file mode 100644 index 0000000000000..ec0d86ac066fd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml @@ -0,0 +1,314 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductCategoryIndexerInUpdateOnScheduleModeTest"> + <annotations> + <stories value="Product Categories Indexer"/> + <title value="Product Categories Indexer in Update on Schedule mode"/> + <description value="The test verifies that in Update on Schedule mode if displaying of category products on Storefront changes due to product properties change, + the changes are NOT applied immediately, but applied only after cron runs (twice)."/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11146"/> + <group value="catalog"/> + <group value="indexer"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!-- Create category A without products --> + <createData entity="_defaultCategory" stepKey="createCategoryA"/> + + <!-- Create product A1 not assigned to any category --> + <createData entity="simpleProductWithoutCategory" stepKey="createProductA1"/> + + <!-- Create anchor category B with subcategory C--> + <createData entity="_defaultCategory" stepKey="createCategoryB"/> + <createData entity="SubCategoryWithParent" stepKey="createCategoryC"> + <requiredEntity createDataKey="createCategoryB"/> + </createData> + + <!-- Assign product B1 to category B --> + <createData entity="ApiSimpleProduct" stepKey="createProductB1"> + <requiredEntity createDataKey="createCategoryB"/> + </createData> + + <!-- Assign product C1 to category C --> + <createData entity="ApiSimpleProduct" stepKey="createProductC1"> + <requiredEntity createDataKey="createCategoryC"/> + </createData> + + <!-- Assign product C2 to category C --> + <createData entity="ApiSimpleProduct" stepKey="createProductC2"> + <requiredEntity createDataKey="createCategoryC"/> + </createData> + + <!-- Switch indexers to "Update by Schedule" mode --> + <actionGroup ref="AdminSwitchAllIndexerToActionModeActionGroup" stepKey="onUpdateBySchedule"> + <argument name="action" value="Update by Schedule"/> + </actionGroup> + </before> + <after> + <!-- Switch indexers to "Update on Save" mode --> + <actionGroup ref="AdminSwitchAllIndexerToActionModeActionGroup" stepKey="onUpdateOnSave"> + <argument name="action" value="Update on Save"/> + </actionGroup> + <!-- Delete data --> + <deleteData createDataKey="createProductA1" stepKey="deleteProductA1"/> + <deleteData createDataKey="createProductB1" stepKey="deleteProductB1"/> + <deleteData createDataKey="createProductC1" stepKey="deleteProductC1"/> + <deleteData createDataKey="createProductC2" stepKey="deleteProductC2"/> + <deleteData createDataKey="createCategoryA" stepKey="deleteCategoryA"/> + <deleteData createDataKey="createCategoryC" stepKey="deleteCategoryC"/> + <deleteData createDataKey="createCategoryB" stepKey="deleteCategoryB"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Case: change product category from product page --> + <!-- 1. Open Admin > Catalog > Products > Product A1 --> + <amOnPage url="{{AdminProductEditPage.url($$createProductA1.id$$)}}" stepKey="goToProductA1"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- 2. Assign category A to product A1. Save product --> + <actionGroup ref="AdminAssignCategoryToProductAndSaveActionGroup" stepKey="assignProduct"> + <argument name="categoryName" value="$$createCategoryA.name$$"/> + </actionGroup> + + <!-- 3. Open category A on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="goToCategoryA"> + <argument name="categoryName" value="$$createCategoryA.name$$"/> + </actionGroup> + + <!-- The category is still empty --> + <see userInput="$$createCategoryA.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryA1Name"/> + <see userInput="We can't find products matching the selection." stepKey="seeEmptyNotice"/> + <dontSee userInput="$$createProductA1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeProductA1"/> + + <!-- 4. Run cron to reindex --> + <wait time="60" stepKey="waitForChanges"/> + <magentoCLI command="cron:run" stepKey="runCron"/> + + <!-- 5. Open category A on Storefront again --> + <reloadPage stepKey="reloadCategoryA"/> + + <!-- Category A displays product A1 now --> + <see userInput="$$createCategoryA.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeTitleCategoryA1"/> + <see userInput="$$createProductA1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductA1"/> + + <!--6. Open Admin > Catalog > Products > Product A1. Unassign category A from product A1 --> + <amOnPage url="{{AdminProductEditPage.url($$createProductA1.id$$)}}" stepKey="OnPageProductA1"/> + <waitForPageLoad stepKey="waitForProductA1PageLoad"/> + <actionGroup ref="AdminUnassignCategoryOnProductAndSaveActionGroup" stepKey="unassignCategoryA"> + <argument name="categoryName" value="$$createCategoryA.name$$"/> + </actionGroup> + + <!-- 7. Open category A on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="toCategoryA"> + <argument name="categoryName" value="$$createCategoryA.name$$"/> + </actionGroup> + + <!-- Category A still contains product A1 --> + <see userInput="$$createCategoryA.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryAOnPage"/> + <see userInput="$$createProductA1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeNameProductA1"/> + + <!-- 8. Run cron reindex (Ensure that at least one minute passed since last cron run) --> + <wait time="60" stepKey="waitOneMinute"/> + <magentoCLI command="cron:run" stepKey="runCron1"/> + + <!-- 9. Open category A on Storefront again --> + <reloadPage stepKey="refreshCategoryAPage"/> + + <!-- Category A is empty now --> + <see userInput="$$createCategoryA.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeOnPageCategoryAName"/> + <see userInput="We can't find products matching the selection." stepKey="seeOnPageEmptyNotice"/> + <dontSee userInput="$$createProductA1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeProductA1OnPage"/> + + <!-- Case: change product status --> + <!-- 10. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="toCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays product B1, C1 and C2 --> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryBOnPage"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeNameProductB1"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeNameProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeNameProductC2"/> + + <!-- 11. Open product C1 in Admin. Make it disabled (Enable Product = No)--> + <amOnPage url="{{AdminProductEditPage.url($$createProductC1.id$$)}}" stepKey="goToProductC1"/> + <waitForPageLoad stepKey="waitForProductC1PageLoad"/> + <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="clickOffEnableToggleAgain"/> + <!-- Saved successfully --> + <actionGroup ref="saveProductForm" stepKey="saveProductC1"/> + + <!-- 12. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="toCategoryBStorefront"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays product B1, C1 and C2 --> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="categoryBOnPage"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductB1"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductC2"/> + + <!-- 13. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="goToCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C still displays products C1 and C2 --> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="categoryCOnPage"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductC1inCategoryC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductC2InCategoryC2"/> + + <!-- 14. Run cron to reindex (Ensure that at least one minute passed since last cron run) --> + <wait time="60" stepKey="waitMinute"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- 15. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="onPageCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays product B1 and C2 only--> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeTitleCategoryBOnPage"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnCategoryBPageProductB1"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeOnCategoryBPageProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnCategoryBPageProductC2"/> + + <!-- 16. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="openCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C displays only product C2 now --> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeTitleOnCategoryCPage"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeOnCategoryCPageProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnCategoryCPageProductC2"/> + + <!-- 17. Repeat steps 10-16, but enable products instead. --> + <!-- 17.11 Open product C1 in Admin. Make it enabled --> + <amOnPage url="{{AdminProductEditPage.url($$createProductC1.id$$)}}" stepKey="goToEditProductC1"/> + <waitForPageLoad stepKey="waitForProductC1Page"/> + <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="clickOnEnableToggleAgain"/> + + <!-- Saved successfully --> + <actionGroup ref="saveProductForm" stepKey="saveChangedProductC1"/> + + <!-- 17.12. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays product B1 and C2 --> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="titleCategoryBOnPage"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryBPageProductB1"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeCategoryBPageProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryBPageProductC2"/> + + <!-- 17.13. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="openToCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C displays product C2 --> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="titleOnCategoryCPage"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeCategoryCPageProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryCPageProductC2"/> + + <!-- 17.14. Run cron to reindex (Ensure that at least one minute passed since last cron run) --> + <wait time="60" stepKey="waitForOneMinute"/> + <magentoCLI command="cron:run" stepKey="runCron3"/> + + <!-- 17.15. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openPageCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays products B1, C1, C2 again, but only after reindex. --> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="onPageSeeCategoryBTitle"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="onPageSeeCategoryBProductB1"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="onPageSeeCategoryBProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="onPageSeeCategoryBProductC2"/> + + <!-- 17.16. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="openOnStorefrontCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C displays products C1, C2 again, but only after reindex.--> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="onPageSeeCategoryCTitle"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="onPageSeeCategoryCProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="onPageSeeCategoryCProductC2"/> + + <!-- Case: change product visibility --> + <!-- 18. Repeat steps 10-17 but change product Visibility instead of product status --> + <!-- 18.11 Open product C1 in Admin. Make it enabled --> + <amOnPage url="{{AdminProductEditPage.url($$createProductC1.id$$)}}" stepKey="editProductC1"/> + <waitForPageLoad stepKey="waitProductC1Page"/> + <selectOption selector="{{AdminProductFormBundleSection.visibilityDropDown}}" userInput="Search" + stepKey="changeVisibility"/> + + <!-- Saved successfully --> + <actionGroup ref="saveProductForm" stepKey="productC1Saved"/> + + <!-- 18.12. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="goPageCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays products B1, C1, C2 again, but only after reindex. --> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryBTitle"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryBProductB1"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryBProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryBProductC2"/> + + <!-- 18.13. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="goPageCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C displays products C1, C2 again, but only after reindex.--> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeCategoryCTitle"/> + <see userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnCategoryCProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnCategoryCProductC2"/> + + <!-- 18.14. Run cron to reindex (Ensure that at least one minute passed since last cron run) --> + <wait time="60" stepKey="waitExtraMinute"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> + + <!-- 18.15. Open category B on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="navigateToPageCategoryB"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + </actionGroup> + + <!-- Category B displays product B1 and C2 only--> + <see userInput="$$createCategoryB.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeTitleCategoryB"/> + <see userInput="$$createProductB1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeTitleProductB1"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeCategoryBProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeTitleProductC2"/> + + <!-- 18.18. Open category C on Storefront --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="navigateToPageCategoryC"> + <argument name="categoryName" value="$$createCategoryB.name$$"/> + <argument name="subCategoryName" value="$$createCategoryC.name$$"/> + </actionGroup> + + <!-- Category C displays product C2 again, but only after reindex.--> + <see userInput="$$createCategoryC.name$$" selector="{{StorefrontCategoryMainSection.CategoryTitle}}" stepKey="seeTitleCategoryC"/> + <dontSee userInput="$$createProductC1.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeOnCategoryCProductC1"/> + <see userInput="$$createProductC2.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeOnPageTitleProductC2"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml new file mode 100644 index 0000000000000..b5f212d1144be --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRemoveCustomOptionsFromProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create product with custom options"/> + <title value="Remove custom options from product"/> + <description value="Remove custom options from product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-11512"/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <updateData entity="ProductWithTextFieldAndAreaAndFileOptions" createDataKey="createProduct" stepKey="updateProductWithOptions"> + <requiredEntity createDataKey="createProduct"/> + </updateData> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProductWithOptions"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearProductFilter"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Edit Simple Product --> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="goToProduct"/> + <!-- See 3 options are present --> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertCustomOptionsField"> + <argument name="option" value="ProductOptionField"/> + </actionGroup> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertCustomOptionsArea"> + <argument name="option" value="ProductOptionArea"/> + </actionGroup> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertCustomOptionsFile"> + <argument name="option" value="ProductOptionFile"/> + </actionGroup> + <!-- Click delete "Area" and "File" options --> + <actionGroup ref="AdminDeleteProductCustomOption" stepKey="deleteCustomOptionArea"> + <argument name="option" value="ProductOptionArea"/> + </actionGroup> + <actionGroup ref="AdminDeleteProductCustomOption" stepKey="deleteCustomOptionFile"> + <argument name="option" value="ProductOptionFile"/> + </actionGroup> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertVisibleCustomOptionField"> + <argument name="option" value="ProductOptionField"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!-- See only "Field option" left Also we shouldn't see any other options --> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertVisibleSecondCustomOptionField"> + <argument name="option" value="ProductOptionField"/> + </actionGroup> + <actionGroup ref="AdminAssertProductHasNoCustomOption" stepKey="assertNoCustomOptionsFile"> + <argument name="option" value="ProductOptionFileSecond"/> + </actionGroup> + <actionGroup ref="AdminAssertProductHasNoCustomOption" stepKey="assertNoCustomOptionsField"> + <argument name="option" value="ProductOptionFieldSecond"/> + </actionGroup> + <!-- Click Add option "File" --> + <actionGroup ref="AddProductCustomOptionFile" stepKey="createAddOptionFile"> + <argument name="option" value="ProductOptionFileSecond"/> + </actionGroup> + <!-- Click Add option "Field" --> + <actionGroup ref="AddProductCustomOptionField" stepKey="createCustomOptionField"> + <argument name="option" value="ProductOptionFieldSecond"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductWithNewlyAddedOptions"/> + <!-- See 3 options are present --> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertPresentCustomOptionField"> + <argument name="option" value="ProductOptionField"/> + </actionGroup> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertPresenceOfFileOption"> + <argument name="option" value="ProductOptionFileSecond"/> + </actionGroup> + <actionGroup ref="AdminAssertProductCustomOptionVisible" stepKey="assertPresenceOfFieldOption"> + <argument name="option" value="ProductOptionFieldSecond"/> + </actionGroup> + <!-- Delete All options and See no more options present on the page --> + <actionGroup ref="AdminDeleteAllProductCustomOptions" stepKey="deleteAllCustomOptions"/> + <!-- Product successfully saved and it has no options --> + <actionGroup ref="saveProductForm" stepKey="saveProductWithoutCustomOptions"/> + <actionGroup ref="AdminAssertProductHasNoCustomOptions" stepKey="assertNoCustomOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml new file mode 100644 index 0000000000000..7b5455951fb27 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRestrictedUserAddCategoryFromProductPageTest"> + <annotations> + <features value="Catalog"/> + <stories value="Category"/> + <title value="Adding new category from product page by restricted user"/> + <description value="Adding new category from product page by restricted user"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17229"/> + <useCaseId value="MAGETWO-69893"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create category--> + <comment userInput="Create category" stepKey="commentCreateCategory"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + </before> + <after> + <!--Delete created product--> + <comment userInput="Delete created product" stepKey="commentDeleteProduct"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteProduct"> + <argument name="sku" value="{{_defaultProduct.sku}}"/> + </actionGroup> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetFiltersIfExist"/> + <actionGroup ref="logout" stepKey="logoutOfUser"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <amOnPage url="{{AdminRolesPage.url}}" stepKey="navigateToUserRoleGrid" /> + <waitForPageLoad stepKey="waitForRolesGridLoad" /> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="deleteUserRole"> + <argument name="role" value="adminRole"/> + </actionGroup> + <amOnPage url="{{AdminUsersPage.url}}" stepKey="goToAllUsersPage"/> + <waitForPageLoad stepKey="waitForUsersGridLoad" /> + <actionGroup ref="AdminDeleteNewUserActionGroup" stepKey="deleteUser"> + <argument name="userName" value="{{admin2.username}}"/> + </actionGroup> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!--Create user role--> + <comment userInput="Create user role" stepKey="commentCreateUserRole"/> + <actionGroup ref="AdminFillUserRoleRequiredData" stepKey="fillUserRoleRequiredData"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Stores"/> + </actionGroup> + <click selector="{{AdminEditRoleInfoSection.roleResourcesTab}}" stepKey="clickRoleResourcesTab" /> + <actionGroup ref="AdminAddRestrictedRole" stepKey="addRestrictedRoleStores"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Stores"/> + </actionGroup> + <actionGroup ref="AdminAddRestrictedRole" stepKey="addRestrictedRoleProducts"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Products"/> + </actionGroup> + <click selector="{{AdminEditRoleInfoSection.saveButton}}" stepKey="clickSaveRoleButton" /> + <see userInput="You saved the role." stepKey="seeUserRoleSavedMessage"/> + <!--Create user and assign role to it--> + <comment userInput="Create user and assign role to it" stepKey="commentCreateUser"/> + <actionGroup ref="AdminCreateUserActionGroup" stepKey="createAdminUser"> + <argument name="role" value="adminRole"/> + <argument name="User" value="admin2"/> + </actionGroup> + <!--Log out of admin and login with newly created user--> + <comment userInput="Log out of admin and login with newly created user" stepKey="commentLoginWithNewUser"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsNewUser"> + <argument name="adminUser" value="admin2"/> + </actionGroup> + <!--Go to create product page--> + <comment userInput="Go to create product page" stepKey="commentGoCreateProductPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"/> + <dontSeeElement selector="{{AdminProductFormSection.newCategoryButton}}" stepKey="dontSeeNewCategoryButton"/> + <!--Fill product data and assign to category--> + <comment userInput="Fill product data and assign to category" stepKey="commentFillProductData"/> + <actionGroup ref="fillMainProductForm" stepKey="fillMainProductForm"/> + <actionGroup ref="SetCategoryByName" stepKey="addCategoryToProduct"> + <argument name="categoryName" value="$$createCategory.name$$"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!--Assert that category exist in field--> + <comment userInput="Assert that category exist in field" stepKey="commentAssertion"/> + <grabTextFrom selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="grabCategoryName"/> + <assertContains stepKey="assertThatCategory"> + <expectedResult type="variable">$$createCategory.name$$</expectedResult> + <actualResult type="variable">$grabCategoryName</actualResult> + </assertContains> + <!--Remove the category from the product and assert that it removed--> + <comment userInput="Remove the category from the product and assert that it removed" stepKey="assertCategoryRemoved"/> + <actionGroup ref="removeCategoryFromProduct" stepKey="removeCategoryFromProduct"> + <argument name="categoryName" value="$$createCategory.name$$"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductAfterRemovingCategory"/> + <grabTextFrom selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="grabCategoryFieldContent"/> + <assertNotContains stepKey="assertThatCategoryRemoved"> + <expectedResult type="variable">$$createCategory.name$$</expectedResult> + <actualResult type="variable">$grabCategoryFieldContent</actualResult> + </assertNotContains> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml new file mode 100644 index 0000000000000..061c30b224828 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminShouldBeAbleToAssociateSimpleProductToWebsitesTest"> + <annotations> + <features value="Catalog"/> + <stories value="Edit products"/> + <title value="Admin should be able to associate simple product to websites"/> + <description value="Admin should be able to associate simple product to websites"/> + <testCaseId value="MC-3483"/> + <group value="catalog"/> + <severity value="AVERAGE"/> + </annotations> + + <before> + <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToYes"/> + <createData entity="secondCustomWebsite" stepKey="createCustomWebsite"/> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminStoreGroupCreateActionGroup" stepKey="createNewStore"> + <argument name="Website" value="secondCustomWebsite"/> + <argument name="storeGroup" value="customStoreGroup"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + </before> + + <after> + <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToNo"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <!-- Delete second website --> + <actionGroup ref="DeleteCustomWebsiteActionGroup" stepKey="deleteCustomWeWebsite"> + <argument name="websiteName" value="$createCustomWebsite.website[name]$"/> + </actionGroup> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="resetFiltersOnStoresIndexPage"/> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPageToResetFilters"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFiltersOnProductIndexPage"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- 1. Go to product page in admin panel to edit --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPageToAssociateToSecondWebsite"/> + <actionGroup ref="filterProductGridByName2" stepKey="filterProductInGrid"> + <argument name="name" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- 2. Go to Product in Websites tab, unassign product from Main website and assign it to Second website --> + <actionGroup ref="AdminProcessProductWebsitesActionGroup" stepKey="processProductWebsites"> + <argument name="website" value="secondCustomWebsite"/> + <argument name="websiteToUnassign" value="_defaultWebsite"/> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertProductIsAssignedToWebsite" stepKey="seeCustomWebsiteIsChecked"> + <argument name="website" value="$createCustomWebsite.website[name]$"/> + </actionGroup> + <actionGroup ref="AssertProductIsNotAssignedToWebsite" stepKey="seeMainWebsiteIsNotChecked"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + </actionGroup> + + <!-- 3. Go to frontend and open Simple product on Main website and assert 404 page--> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$createSimpleProduct.custom_attributes[url_key]$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup" stepKey="assertPageNotFoundErrorOnProductDetailPage"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!-- 4. Open Simple product on Second website and assert its name --> + <actionGroup ref="StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup" stepKey="openProductPageUsingStoreCodeInUrl"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml index b06502ce94c65..4803c1be6c936 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml @@ -22,6 +22,11 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <createData entity="SimpleProduct2" stepKey="simpleProduct0"/> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"/> </before> <after> <!-- Delete simple product --> @@ -31,6 +36,11 @@ <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> <deleteData createDataKey="simpleProduct0" stepKey="deleteSimpleProduct0"/> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteSimpleProduct6"/> </after> <!--Create product--> @@ -69,6 +79,28 @@ <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedSee"/> <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedRelatedProduct}}" userInput="$$simpleProduct1.sku$$" stepKey="seeRelatedProduct"/> + <!--See more related products in admin--> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct2"> + <argument name="sku" value="$$simpleProduct2.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct3"> + <argument name="sku" value="$$simpleProduct3.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct4"> + <argument name="sku" value="$$simpleProduct4.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct5"> + <argument name="sku" value="$$simpleProduct5.sku$$"/> + </actionGroup> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct6"> + <argument name="sku" value="$$simpleProduct6.sku$$"/> + </actionGroup> + <scrollTo selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" stepKey="scrollTo2"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedSee2"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedRelatedProduct}}" userInput="$$simpleProduct6.sku$$" stepKey="seeSixthRelatedProduct"/> + <selectOption selector=".admin__collapsible-block-wrapper[data-index='related'] .admin__control-select" userInput="5" stepKey="selectFivePerPage"/> + <dontSee selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedRelatedProduct}}" userInput="$$simpleProduct6.sku$$" stepKey="dontSeeSixthRelatedProduct"/> + <!--See related product in storefront--> <amOnPage url="{{SimpleProduct3.sku}}.html" stepKey="goToStorefront"/> <waitForPageLoad stepKey="waitForStorefront"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml index 234a7c69913c9..eb014ca7f884d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml @@ -13,6 +13,7 @@ <stories value="View sorting by websites"/> <title value="Sorting by websites in Admin"/> <description value="Sorting products by websites in Admin"/> + <severity value="AVERAGE"/> </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml index 51af9d78dfd46..4bcb82372e801 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml @@ -11,6 +11,7 @@ <test name="AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest"> <annotations> <features value="Catalog"/> + <stories value="Tier price"/> <title value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <description value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index a33c7bb12879a..bcc8636c65b1e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -34,6 +34,7 @@ </before> <after> <deleteData createDataKey="attribute" stepKey="deleteAttribute"/> + <deleteData createDataKey="product" stepKey="deleteProduct"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Assert attribute presence in storefront product additional information --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml index 18e4ff9ee2c99..80f0c8ad10ede 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml @@ -52,9 +52,14 @@ <!-- Assign simple product to created store view --> <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" stepKey="clickCategoryStoreViewDropdownToggle"/> + <waitForPageLoad stepKey="waitForStoreViewDropdown"/> + <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption(customStoreFR.name)}}" stepKey="waitForStoreViewOption"/> <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption(customStoreFR.name)}}" stepKey="selectCategoryStoreViewOption"/> + <waitForPageLoad stepKey="waitForAcceptModal"/> + <waitForElementVisible selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="waitForAcceptButton"/> <click selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="clickAcceptButton"/> <waitForPageLoad stepKey="waitForThePageToLoad"/> + <waitForElementNotVisible selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="waitForAcceptButtonGone"/> <uncheckOption selector="{{AdminProductFormSection.productNameUseDefault}}" stepKey="uncheckProductStatus"/> <!-- Update default simple product with name --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml index d5fc981b5b2e6..f698b3d89ffe9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml @@ -52,7 +52,11 @@ <!-- Assign simple product to created store view --> <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" stepKey="clickCategoryStoreViewDropdownToggle"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption(customStoreFR.name)}}" stepKey="waitForSelectCategoryStoreViewOption"/> <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption(customStoreFR.name)}}" stepKey="selectCategoryStoreViewOption"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <waitForElementVisible selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="waitForAcceptButton"/> <click selector="{{AdminProductFormChangeStoreSection.acceptButton}}" stepKey="clickAcceptButton"/> <waitForPageLoad stepKey="waitForPageToLoad"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml index 2c3aa5db75171..f317c66e5366a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml @@ -65,8 +65,10 @@ <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="{{simpleProductTierPrice300InStock.weightSelect}}" stepKey="selectProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml index a042c4d60ae4f..afb8b40a6dbd4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml @@ -62,8 +62,10 @@ <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="{{simpleProductEnabledFlat.weightSelect}}" stepKey="selectProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{simpleProductEnabledFlat.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml index d08ef9c93999c..2436fc0fc7f12 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductNotVisibleIndividually.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory"/> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{simpleProductNotVisibleIndividually.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml index a695982921cfd..637ae790c16c8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductRegularPrice245InStock.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{simpleProductRegularPrice245InStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml index ba52c6d2bc261..045b3f3420ff6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductRegularPrice32501InStock.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory"/> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory"/> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{simpleProductRegularPrice32501InStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml index cb5c24839e387..214f9b0273b6a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductRegularPrice325InStock.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{simpleProductRegularPrice325InStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml index 318ab6555235e..b145328890a91 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductRegularPriceCustomOptions.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory"/> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory"/> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> @@ -150,6 +152,7 @@ <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="1" stepKey="fillProductQuantity"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeYouAddedSimpleprod4ToYourShoppingCartSuccessSaveMessage"/> <seeElement selector="{{StorefrontMinicartSection.quantity(1)}}" stepKey="seeAddedProductQuantityInCart"/> <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml index 54ed753b80a1c..27c7e77a94ad1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml @@ -53,8 +53,10 @@ <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{simpleProductRegularPrice32503OutOfStock.weight}}" stepKey="fillSimpleProductWeight"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory"/> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory"/> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml index 9bdc93e61e499..8ac56d09e5b42 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml @@ -62,8 +62,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml index 34d85e7b46850..f621813f4f8c0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml @@ -52,8 +52,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPriceInStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPriceInStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml index e64022b311614..ffbad0752b73c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml @@ -51,8 +51,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml index 9b6a56d6f81d8..3101c1e460322 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml @@ -57,8 +57,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductSpecialPrice.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPrice.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml index 920a0a494bae5..58978c31b5b40 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -56,8 +56,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml index d4ec5e410d9ff..d28e9ddbb1271 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml @@ -62,8 +62,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductTierPriceInStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductTierPriceInStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml index 717d710b4a288..22dd2b0054db4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml @@ -62,8 +62,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductWithTierPriceInStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductWithTierPriceInStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml index 703a4e24cdca9..29c7536d21621 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -62,8 +62,10 @@ <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualTierPriceOutOfStock.status}}" stepKey="selectStockStatusInStock"/> <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <waitForPageLoad stepKey="waitForCategory1"/> <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <waitForPageLoad stepKey="waitForCategory2"/> <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualTierPriceOutOfStock.visibility}}" stepKey="selectVisibility"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index 7c0de6da18caf..e3924099d2f27 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -17,6 +17,9 @@ <description value="User browses catalog, searches for product, adds product to cart, adds product to wishlist, compares products, uses coupon code and checks out."/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-87435"/> + <skip> + <issueId value="MC-16684"/> + </skip> </annotations> <before> <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml index 3dd55a9dfee92..461ebde29fcad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml @@ -11,6 +11,7 @@ <test name="ProductAvailableAfterEnablingSubCategoriesTest"> <annotations> <features value="Catalog"/> + <stories value="Categories"/> <title value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> <description value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml index fb95fc3f57bca..a0670bdee54c7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml @@ -15,7 +15,7 @@ <title value="Admin should be able to sell products with different variants of their own"/> <description value="Admin should be able to sell products with different variants of their own"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-58184"/> + <testCaseId value="MC-16476"/> <group value="product"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index a3bce2d4fe2f2..12a465188aa85 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -12,10 +12,10 @@ <annotations> <features value="Catalog"/> <stories value="Purchase a product with Custom Options of different types"/> - <title value="Admin should be able to sell products with different variants of their own"/> - <description value="Admin should be able to sell products with different variants of their own"/> + <title value="Admin should be able to sell products with custom options of different types"/> + <description value="Admin should be able to sell products with custom options of different types"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-61717"/> + <testCaseId value="MC-16462"/> <group value="Catalog"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml index 04cb813ec0efb..294dcb8c1b81a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -102,6 +102,7 @@ <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> <!-- Checking the correctness of displayed custom options for user parameters on Order --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml index 268e18d2b4efa..514e12bb355a8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest"> <annotations> <features value="Catalog"/> + <stories value="Special price"/> <title value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> <description value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php index 249c32ff276c3..9a2199859a1df 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Entity\Attribute; use Magento\Catalog\Model\Product; use Magento\Framework\Phrase; +use Magento\MediaStorage\Helper\File\Storage\Database; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -50,6 +51,11 @@ class ContentTest extends \PHPUnit\Framework\TestCase */ protected $imageHelper; + /** + * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject + */ + protected $databaseMock; + /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ @@ -71,13 +77,18 @@ public function setUp() ->disableOriginalConstructor() ->getMock(); + $this->databaseMock = $this->getMockBuilder(Database::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->content = $this->objectManager->getObject( \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content::class, [ 'mediaConfig' => $this->mediaConfigMock, 'jsonEncoder' => $this->jsonEncoderMock, - 'filesystem' => $this->fileSystemMock + 'filesystem' => $this->fileSystemMock, + 'fileStorageDatabase' => $this->databaseMock ] ); } @@ -143,6 +154,13 @@ public function testGetImagesJson() $this->readMock->expects($this->any())->method('stat')->willReturnMap($sizeMap); $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); } @@ -210,6 +228,14 @@ public function testGetImagesJsonWithException() $this->fileSystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($this->readMock); $this->mediaConfigMock->expects($this->any())->method('getMediaUrl'); $this->mediaConfigMock->expects($this->any())->method('getMediaPath'); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + $this->readMock->expects($this->any())->method('stat')->willReturnOnConsecutiveCalls( $this->throwException( new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) @@ -365,4 +391,52 @@ private function getMediaAttribute(string $label, string $attributeCode) return $mediaAttribute; } + + /** + * Test GetImagesJson() calls MediaStorage functions to obtain image from DB prior to stat call + * + * @return void + */ + public function testGetImagesJsonMediaStorageMode() + { + $images = [ + 'images' => [ + [ + 'value_id' => '0', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '0' + ] + ] + ]; + + $mediaPath = [ + ['file_1.jpg', 'catalog/product/image_1.jpg'] + ]; + + $this->content->setElement($this->galleryMock); + + $this->galleryMock->expects($this->once()) + ->method('getImages') + ->willReturn($images); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->willReturn($this->readMock); + $this->mediaConfigMock->expects($this->any()) + ->method('getMediaPath') + ->willReturnMap($mediaPath); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(false)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(true)); + + $this->databaseMock->expects($this->once()) + ->method('saveFileToFilesystem') + ->with('catalog/product/image_1.jpg'); + + $this->content->getImagesJson(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php index af208c016c7e3..6563bdeb149e1 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php @@ -8,6 +8,9 @@ namespace Magento\Catalog\Test\Unit\Block\Product; +/** + * Class ViewTest + */ class ViewTest extends \PHPUnit\Framework\TestCase { /** @@ -25,6 +28,9 @@ class ViewTest extends \PHPUnit\Framework\TestCase */ protected $registryMock; + /** + * @inheritDoc + */ protected function setUp() { $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -36,6 +42,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testShouldRenderQuantity() { $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); @@ -61,28 +70,26 @@ public function testShouldRenderQuantity() $this->assertEquals(false, $this->view->shouldRenderQuantity()); } + /** + * @return void + */ public function testGetIdentities() { $productTags = ['cat_p_1']; $product = $this->createMock(\Magento\Catalog\Model\Product::class); - $category = $this->createMock(\Magento\Catalog\Model\Category::class); $product->expects($this->once()) ->method('getIdentities') ->will($this->returnValue($productTags)); - $category->expects($this->once()) - ->method('getId') - ->will($this->returnValue(1)); $this->registryMock->expects($this->any()) ->method('registry') ->will( $this->returnValueMap( [ ['product', $product], - ['current_category', $category], ] ) ); - $this->assertEquals(['cat_p_1', 'cat_c_1'], $this->view->getIdentities()); + $this->assertEquals($productTags, $this->view->getIdentities()); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php index f1672d842de4e..dc74cdfc642e3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php @@ -6,7 +6,12 @@ namespace Magento\Catalog\Test\Unit\Model\Category\Attribute\Backend; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\WriteInterface; +/** + * Test for Magento\Catalog\Model\Category\Attribute\Backend\Image class. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ImageTest extends \PHPUnit\Framework\TestCase { /** @@ -67,7 +72,7 @@ protected function setUp() $this->imageUploader = $this->createPartialMock( \Magento\Catalog\Model\ImageUploader::class, - ['moveFileFromTmp'] + ['moveFileFromTmp', 'getBasePath'] ); $this->filesystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class)->disableOriginalConstructor() @@ -95,9 +100,7 @@ public function testBeforeSaveValueDeletion($value) $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); $model->setAttribute($this->attribute); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => $value - ]); + $object = new \Magento\Framework\DataObject(['test_attribute' => $value]); $model->beforeSave($object); @@ -132,9 +135,7 @@ public function testBeforeSaveValueInvalid($value) $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); $model->setAttribute($this->attribute); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => $value - ]); + $object = new \Magento\Framework\DataObject(['test_attribute' => $value]); $model->beforeSave($object); @@ -146,14 +147,25 @@ public function testBeforeSaveValueInvalid($value) */ public function testBeforeSaveAttributeFileName() { - $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); - $model->setAttribute($this->attribute); + $model = $this->setUpModelForAfterSave(); + $mediaDirectoryMock = $this->createMock(WriteInterface::class); + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($mediaDirectoryMock); + $this->imageUploader->expects($this->once())->method('getBasePath')->willReturn('base/path'); + $mediaDirectoryMock->expects($this->once()) + ->method('getAbsolutePath') + ->with('base/path/test123.jpg') + ->willReturn('absolute/path/base/path/test123.jpg'); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => [ - ['name' => 'test123.jpg'] + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => [ + ['name' => 'test123.jpg'], + ], ] - ]); + ); $model->beforeSave($object); @@ -165,30 +177,37 @@ public function testBeforeSaveAttributeFileName() */ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() { - $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [ - 'filesystem' => $this->filesystem - ]); - + $model = $this->setUpModelForAfterSave(); $model->setAttribute($this->attribute); + $mediaDirectoryMock = $this->createMock(WriteInterface::class); + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($mediaDirectoryMock); $this->filesystem ->expects($this->once()) ->method('getUri') ->with(DirectoryList::MEDIA) ->willReturn('pub/media'); + $mediaDirectoryMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/pub/media/wysiwyg/test123.jpg'); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => [ - [ - 'name' => '/test123.jpg', - 'url' => '/pub/media/wysiwyg/test123.jpg', - ] + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => [ + [ + 'name' => 'test123.jpg', + 'url' => '/pub/media/wysiwyg/test123.jpg', + ], + ], ] - ]); + ); $model->beforeSave($object); - $this->assertEquals('/pub/media/wysiwyg/test123.jpg', $object->getTestAttribute()); + $this->assertEquals('test123.jpg', $object->getTestAttribute()); $this->assertEquals( [['name' => '/pub/media/wysiwyg/test123.jpg', 'url' => '/pub/media/wysiwyg/test123.jpg']], $object->getData('_additional_data_test_attribute') @@ -200,20 +219,31 @@ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() */ public function testBeforeSaveTemporaryAttribute() { - $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model = $this->setUpModelForAfterSave(); $model->setAttribute($this->attribute); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => [ - ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] + $mediaDirectoryMock = $this->createMock(WriteInterface::class); + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($mediaDirectoryMock); + + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => [ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'], + ], ] - ]); + ); $model->beforeSave($object); - $this->assertEquals([ - ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] - ], $object->getData('_additional_data_test_attribute')); + $this->assertEquals( + [ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'], + ], + $object->getData('_additional_data_test_attribute') + ); } /** @@ -224,9 +254,7 @@ public function testBeforeSaveAttributeStringValue() $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); $model->setAttribute($this->attribute); - $object = new \Magento\Framework\DataObject([ - 'test_attribute' => 'test123.jpg' - ]); + $object = new \Magento\Framework\DataObject(['test_attribute' => 'test123.jpg']); $model->beforeSave($object); @@ -245,18 +273,26 @@ private function setUpModelForAfterSave() $objectManagerMock->expects($this->any()) ->method('get') - ->will($this->returnCallback(function ($class, $params = []) use ($imageUploaderMock) { - if ($class == \Magento\Catalog\CategoryImageUpload::class) { - return $imageUploaderMock; - } - - return $this->objectManager->get($class, $params); - })); - - $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [ - 'objectManager' => $objectManagerMock, - 'logger' => $this->logger - ]); + ->will( + $this->returnCallback( + function ($class, $params = []) use ($imageUploaderMock) { + if ($class == \Magento\Catalog\CategoryImageUpload::class) { + return $imageUploaderMock; + } + + return $this->objectManager->get($class, $params); + } + ) + ); + + $model = $this->objectManager->getObject( + \Magento\Catalog\Model\Category\Attribute\Backend\Image::class, + [ + 'objectManager' => $objectManagerMock, + 'logger' => $this->logger, + 'filesystem' => $this->filesystem, + ] + ); $this->objectManager->setBackwardCompatibleProperty($model, 'imageUploader', $this->imageUploader); return $model->setAttribute($this->attribute); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php index 8ca823127e66c..6c6a69ec39c85 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php @@ -9,31 +9,41 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\File\Mime; use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class FileInfoTest extends \PHPUnit\Framework\TestCase +/** + * Test for Magento\Catalog\Model\Category\FileInfo class. + */ +class FileInfoTest extends TestCase { /** - * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + * @var Filesystem|MockObject */ private $filesystem; /** - * @var Mime|\PHPUnit_Framework_MockObject_MockObject + * @var Mime|MockObject */ private $mime; /** - * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + * @var WriteInterface|MockObject */ private $mediaDirectory; /** - * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ReadInterface|MockObject */ private $baseDirectory; + /** + * @var ReadInterface|MockObject + */ + private $pubDirectory; + /** * @var FileInfo */ @@ -44,30 +54,43 @@ protected function setUp() $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class) ->getMockForAbstractClass(); - $this->baseDirectory = $this->getMockBuilder(ReadInterface::class) + $this->baseDirectory = $baseDirectory = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + + $this->pubDirectory = $pubDirectory = $this->getMockBuilder(ReadInterface::class) ->getMockForAbstractClass(); $this->filesystem = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystem->expects($this->any()) - ->method('getDirectoryWrite') + + $this->filesystem->method('getDirectoryWrite') ->with(DirectoryList::MEDIA) ->willReturn($this->mediaDirectory); - $this->filesystem->expects($this->any()) - ->method('getDirectoryRead') - ->with(DirectoryList::ROOT) - ->willReturn($this->baseDirectory); + $this->filesystem->method('getDirectoryRead') + ->willReturnCallback( + function ($arg) use ($baseDirectory, $pubDirectory) { + if ($arg === DirectoryList::PUB) { + return $pubDirectory; + } + return $baseDirectory; + } + ); $this->mime = $this->getMockBuilder(Mime::class) ->disableOriginalConstructor() ->getMock(); - $this->baseDirectory->expects($this->any()) - ->method('getAbsolutePath') - ->with(null) - ->willReturn('/a/b/c'); + $this->baseDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/'); + + $this->baseDirectory->method('getRelativePath') + ->with('/a/b/c/pub/') + ->willReturn('pub/'); + + $this->pubDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/'); $this->model = new FileInfo( $this->filesystem, @@ -85,12 +108,12 @@ public function testGetMimeType() $this->mediaDirectory->expects($this->at(0)) ->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); $this->mediaDirectory->expects($this->at(1)) ->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); $this->mediaDirectory->expects($this->at(2)) ->method('getAbsolutePath') @@ -113,13 +136,11 @@ public function testGetStat() $expected = ['size' => 1]; - $this->mediaDirectory->expects($this->any()) - ->method('getAbsolutePath') + $this->mediaDirectory->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); - $this->mediaDirectory->expects($this->once()) - ->method('stat') + $this->mediaDirectory->method('stat') ->with($mediaPath . $fileName) ->willReturn($expected); @@ -130,22 +151,52 @@ public function testGetStat() $this->assertEquals(1, $result['size']); } - public function testIsExist() + /** + * @param $fileName + * @param $fileMediaPath + * @dataProvider isExistProvider + */ + public function testIsExist($fileName, $fileMediaPath) { - $mediaPath = '/catalog/category'; + $this->mediaDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/media/'); - $fileName = '/filename.ext1'; - - $this->mediaDirectory->expects($this->any()) - ->method('getAbsolutePath') - ->with(null) - ->willReturn('/a/b/c/pub/media'); - - $this->mediaDirectory->expects($this->once()) - ->method('isExist') - ->with($mediaPath . $fileName) + $this->mediaDirectory->method('isExist') + ->with($fileMediaPath) ->willReturn(true); $this->assertTrue($this->model->isExist($fileName)); } + + public function isExistProvider() + { + return [ + ['/filename.ext1', '/catalog/category/filename.ext1'], + ['/pub/media/filename.ext1', 'filename.ext1'], + ['/media/filename.ext1', 'filename.ext1'] + ]; + } + + /** + * @param $fileName + * @param $expected + * @dataProvider isBeginsWithMediaDirectoryPathProvider + */ + public function testIsBeginsWithMediaDirectoryPath($fileName, $expected) + { + $this->mediaDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/media/'); + + $this->assertEquals($expected, $this->model->isBeginsWithMediaDirectoryPath($fileName)); + } + + public function isBeginsWithMediaDirectoryPathProvider() + { + return [ + ['/pub/media/test/filename.ext1', true], + ['/media/test/filename.ext1', true], + ['/test/filename.ext1', false], + ['test2/filename.ext1', false] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php index b8b76524099f4..c7821f06985bc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Test\Unit\Model; use Magento\Catalog\Api\CategoryRepositoryInterface; @@ -52,6 +55,9 @@ class CategoryListTest extends \PHPUnit\Framework\TestCase */ private $collectionProcessorMock; + /** + * @inheritdoc + */ protected function setUp() { $this->categoryCollectionFactory = $this->getMockBuilder(CollectionFactory::class) @@ -93,7 +99,12 @@ public function testGetList() $collection = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock(); $collection->expects($this->once())->method('getSize')->willReturn($totalCount); - $collection->expects($this->once())->method('getAllIds')->willReturn([$categoryIdFirst, $categoryIdSecond]); + $collection->expects($this->once())->method('getData')->willReturn( + [['entity_id' => $categoryIdFirst], ['entity_id' => $categoryIdSecond]] + ); + $collection->expects($this->any())->method('getEntity')->willReturn( + new \Magento\Framework\DataObject(['id_field_name' => 'entity_id']) + ); $this->collectionProcessorMock->expects($this->once()) ->method('process') @@ -106,10 +117,7 @@ public function testGetList() $this->categoryRepository->expects($this->exactly(2)) ->method('get') - ->willReturnMap([ - [$categoryIdFirst, $categoryFirst], - [$categoryIdSecond, $categorySecond], - ]) + ->willReturnMap([[$categoryIdFirst, $categoryFirst], [$categoryIdSecond, $categorySecond]]) ->willReturn($categoryFirst); $this->categorySearchResultsFactory->expects($this->once())->method('create')->willReturn($searchResult); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index cb92cc6c2d523..0dc294e139d3e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -527,12 +527,14 @@ private function getProductMocksForReducedCache($productsCount) for ($i = 1; $i <= $productsCount; $i++) { $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() - ->setMethods([ + ->setMethods( + [ 'getId', 'getSku', 'load', 'setData', - ]) + ] + ) ->getMock(); $productMock->expects($this->once())->method('load'); $productMock->expects($this->atLeastOnce())->method('getId')->willReturn($i); @@ -679,7 +681,7 @@ public function testSaveException() ->willReturn(true); $this->resourceModel->expects($this->once())->method('save')->with($this->product) ->willThrowException(new \Magento\Eav\Model\Entity\Attribute\Exception(__('123'))); - $this->product->expects($this->once())->method('getId')->willReturn(null); + $this->product->expects($this->exactly(2))->method('getId')->willReturn(null); $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') @@ -703,7 +705,7 @@ public function testSaveInvalidProductException() $this->initializationHelper->expects($this->never())->method('initialize'); $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(['error1', 'error2']); - $this->product->expects($this->never())->method('getId'); + $this->product->expects($this->once())->method('getId')->willReturn(null); $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') @@ -1340,11 +1342,13 @@ public function testSaveWithDifferentWebsites() ->willReturn($storeMock); $this->storeManager->expects($this->once()) ->method('getWebsites') - ->willReturn([ + ->willReturn( + [ 1 => ['first'], 2 => ['second'], 3 => ['third'] - ]); + ] + ); $this->product->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]); $this->product->method('getSku')->willReturn('simple'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 22ba6bfa9f7fd..2ef848ca5aada 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -484,9 +484,11 @@ public function testGetCategoryCollectionCollectionNull($initCategoryCollection, $abstractDbMock = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class) ->disableOriginalConstructor() - ->setMethods([ + ->setMethods( + [ 'getCategoryCollection', - ]) + ] + ) ->getMockForAbstractClass(); $getCategoryCollectionMock = $this->createMock( \Magento\Framework\Data\Collection::class @@ -1217,8 +1219,10 @@ public function testSetMediaGalleryEntries() public function testGetMediaGalleryImagesMerging() { - $mediaEntries = [ - 'images' => [ + $mediaEntries = + [ + 'images' => + [ [ 'value_id' => 1, 'file' => 'imageFile.jpg', @@ -1233,24 +1237,28 @@ public function testGetMediaGalleryImagesMerging() 'file' => 'smallImageFile.jpg', 'media_type' => 'image', ], - ] - ]; - $expectedImageDataObject = new \Magento\Framework\DataObject([ + ] + ]; + $expectedImageDataObject = new \Magento\Framework\DataObject( + [ 'value_id' => 1, 'file' => 'imageFile.jpg', 'media_type' => 'image', 'url' => 'http://magento.dev/pub/imageFile.jpg', 'id' => 1, 'path' => '/var/www/html/pub/imageFile.jpg', - ]); - $expectedSmallImageDataObject = new \Magento\Framework\DataObject([ + ] + ); + $expectedSmallImageDataObject = new \Magento\Framework\DataObject( + [ 'value_id' => 2, 'file' => 'smallImageFile.jpg', 'media_type' => 'image', 'url' => 'http://magento.dev/pub/smallImageFile.jpg', 'id' => 2, 'path' => '/var/www/html/pub/smallImageFile.jpg', - ]); + ] + ); $directoryMock = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); $directoryMock->method('getAbsolutePath')->willReturnOnConsecutiveCalls( diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php index 4fce12dc2de89..af2cb6f06ed5a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php @@ -16,6 +16,10 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; use Magento\Framework\DB\Query\BatchIteratorInterface; +/** + * Class ImageTest + * @package Magento\Catalog\Test\Unit\Model\ResourceModel\Product + */ class ImageTest extends \PHPUnit\Framework\TestCase { /** @@ -76,6 +80,37 @@ protected function getVisibleImagesSelectMock(): MockObject return $selectMock; } + /** + * @return MockObject + */ + protected function getUsedImagesSelectMock(): MockObject + { + $selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + $selectMock->expects($this->once()) + ->method('distinct') + ->willReturnSelf(); + $selectMock->expects($this->once()) + ->method('from') + ->with( + ['images' => Gallery::GALLERY_TABLE], + 'value as filepath' + )->willReturnSelf(); + $selectMock->expects($this->once()) + ->method('joinInner') + ->with( + ['image_value' => Gallery::GALLERY_VALUE_TABLE], + 'images.value_id = image_value.value_id' + )->willReturnSelf(); + $selectMock->expects($this->once()) + ->method('where') + ->with('images.disabled = 0 AND image_value.disabled = 0') + ->willReturnSelf(); + + return $selectMock; + } + /** * @param int $imagesCount * @dataProvider dataProvider @@ -116,15 +151,53 @@ public function testGetCountAllProductImages(int $imagesCount): void ); } + /** + * @param int $imagesCount + * @dataProvider dataProvider + */ + public function testGetCountUsedProductImages(int $imagesCount): void + { + $selectMock = $this->getUsedImagesSelectMock(); + $selectMock->expects($this->exactly(2)) + ->method('reset') + ->withConsecutive( + ['columns'], + ['distinct'] + )->willReturnSelf(); + $selectMock->expects($this->once()) + ->method('columns') + ->with(new \Zend_Db_Expr('count(distinct value)')) + ->willReturnSelf(); + + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($selectMock); + $this->connectionMock->expects($this->once()) + ->method('fetchOne') + ->with($selectMock) + ->willReturn($imagesCount); + + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'generator' => $this->generatorMock, + 'resourceConnection' => $this->resourceMock + ] + ); + + $this->assertSame( + $imagesCount, + $imageModel->getCountUsedProductImages() + ); + } + /** * @param int $imagesCount * @param int $batchSize * @dataProvider dataProvider */ - public function testGetAllProductImages( - int $imagesCount, - int $batchSize - ): void { + public function testGetAllProductImages(int $imagesCount, int $batchSize): void + { $this->connectionMock->expects($this->once()) ->method('select') ->willReturn($this->getVisibleImagesSelectMock()); @@ -165,6 +238,54 @@ public function testGetAllProductImages( $this->assertCount($imagesCount, $imageModel->getAllProductImages()); } + /** + * @param int $imagesCount + * @param int $batchSize + * @dataProvider dataProvider + */ + public function testGetUsedProductImages(int $imagesCount, int $batchSize): void + { + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->getUsedImagesSelectMock()); + + $batchCount = (int)ceil($imagesCount / $batchSize); + $fetchResultsCallback = $this->getFetchResultCallbackForBatches($imagesCount, $batchSize); + $this->connectionMock->expects($this->exactly($batchCount)) + ->method('fetchAll') + ->will($this->returnCallback($fetchResultsCallback)); + + /** @var Select | MockObject $selectMock */ + $selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->generatorMock->expects($this->once()) + ->method('generate') + ->with( + 'value_id', + $selectMock, + $batchSize, + BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR + )->will( + $this->returnCallback( + $this->getBatchIteratorCallback($selectMock, $batchCount) + ) + ); + + /** @var Image $imageModel */ + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'generator' => $this->generatorMock, + 'resourceConnection' => $this->resourceMock, + 'batchSize' => $batchSize + ] + ); + + $this->assertCount($imagesCount, $imageModel->getUsedProductImages()); + } + /** * @param int $imagesCount * @param int $batchSize diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/TierPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/TierPriceTest.php index 7804104490958..ade8829b278ae 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/TierPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/TierPriceTest.php @@ -393,4 +393,40 @@ public function dataProviderGetSavePercent() ['basePrice' => '20.80', 'tierPrice' => '18.72', 'savedPercent' => '10'] ]; } + + /** + * @param null|string|float $quantity + * @param float $expectedValue + * @dataProvider getQuantityDataProvider + */ + public function testGetQuantity($quantity, $expectedValue) + { + $tierPrice = new TierPrice( + $this->product, + $quantity, + $this->calculator, + $this->priceCurrencyMock, + $this->session, + $this->groupManagement, + $this->customerGroupRetriever + ); + + $this->assertEquals($expectedValue, $tierPrice->getQuantity()); + } + + /** + * @return array + */ + public function getQuantityDataProvider() + { + return [ + [null, 1], + ['one', 1], + ['', 1], + [4, 4], + [4.5, 4.5], + ['0.7', 0.7], + ['0.0000000', 1] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php index 774edcfeb6b64..45d911f7e94e5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -111,6 +111,7 @@ public function testCreateWithNotFilterableInGridAttribute(array $filterModifier 'visible' => null, 'filter' => $filter, 'component' => 'Magento_Ui/js/grid/columns/column', + '__disableTmpl' => ['label' => true] ], ], 'context' => $this->context, diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php index dcd50d4739d70..c704d9f89581d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php @@ -12,6 +12,9 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Element\UiComponent\ContextInterface; +/** + * MassAction test + */ class MassActionTest extends \PHPUnit\Framework\TestCase { /** @@ -103,7 +106,8 @@ public function getPrepareDataProvider() : array [ 'type' => 'first_action', 'label' => 'First Action', - 'url' => '/module/controller/firstAction' + 'url' => '/module/controller/firstAction', + '__disableTmpl' => true ], ], [ @@ -122,7 +126,8 @@ public function getPrepareDataProvider() : array 'label' => 'Second Sub Action 2', 'url' => '/module/controller/secondSubAction2' ], - ] + ], + '__disableTmpl' => true ], ], [ @@ -141,7 +146,8 @@ public function getPrepareDataProvider() : array 'label' => 'Second Sub Action 2', 'url' => '/module/controller/disable' ], - ] + ], + '__disableTmpl' => true ], ], [ @@ -160,7 +166,8 @@ public function getPrepareDataProvider() : array 'label' => 'Second Sub Action 2', 'url' => '/module/controller/disable' ], - ] + ], + '__disableTmpl' => true ], false, false @@ -170,7 +177,8 @@ public function getPrepareDataProvider() : array [ 'type' => 'delete', 'label' => 'First Action', - 'url' => '/module/controller/delete' + 'url' => '/module/controller/delete', + '__disableTmpl' => true ], ], [ @@ -178,7 +186,8 @@ public function getPrepareDataProvider() : array [ 'type' => 'delete', 'label' => 'First Action', - 'url' => '/module/controller/delete' + 'url' => '/module/controller/delete', + '__disableTmpl' => true ], false, false @@ -188,7 +197,8 @@ public function getPrepareDataProvider() : array [ 'type' => 'delete', 'label' => 'First Action', - 'url' => '/module/controller/attributes' + 'url' => '/module/controller/attributes', + '__disableTmpl' => true ], ], [ @@ -196,7 +206,8 @@ public function getPrepareDataProvider() : array [ 'type' => 'delete', 'label' => 'First Action', - 'url' => '/module/controller/attributes' + 'url' => '/module/controller/attributes', + '__disableTmpl' => true ], false, false diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AdvancedPricingTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AdvancedPricingTest.php index 1a23aaace6e0f..e455ad47ee626 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AdvancedPricingTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AdvancedPricingTest.php @@ -106,7 +106,9 @@ protected function setUp() */ protected function createModel() { - return $this->objectManager->getObject(AdvancedPricing::class, [ + return $this->objectManager->getObject( + AdvancedPricing::class, + [ 'locator' => $this->locatorMock, 'storeManager' => $this->storeManagerMock, 'groupRepository' => $this->groupRepositoryMock, @@ -114,7 +116,8 @@ protected function createModel() 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, 'moduleManager' => $this->moduleManagerMock, 'directoryHelper' => $this->directoryHelperMock - ]); + ] + ); } public function testModifyMeta() diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php index a2d81854607a0..932b09f7df9cb 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Categories; @@ -12,6 +14,7 @@ use Magento\Framework\DB\Helper as DbHelper; use Magento\Framework\UrlInterface; use Magento\Store\Model\Store; +use Magento\Framework\AuthorizationInterface; /** * Class CategoriesTest @@ -45,6 +48,11 @@ class CategoriesTest extends AbstractModifierTest */ protected $categoryCollectionMock; + /** + * @var AuthorizationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $authorizationMock; + protected function setUp() { parent::setUp(); @@ -63,6 +71,9 @@ protected function setUp() $this->categoryCollectionMock = $this->getMockBuilder(CategoryCollection::class) ->disableOriginalConstructor() ->getMock(); + $this->authorizationMock = $this->getMockBuilder(AuthorizationInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->categoryCollectionFactoryMock->expects($this->any()) ->method('create') @@ -86,11 +97,15 @@ protected function setUp() */ protected function createModel() { - return $this->objectManager->getObject(Categories::class, [ - 'locator' => $this->locatorMock, - 'categoryCollectionFactory' => $this->categoryCollectionFactoryMock, - 'arrayManager' => $this->arrayManagerMock, - ]); + return $this->objectManager->getObject( + Categories::class, + [ + 'locator' => $this->locatorMock, + 'categoryCollectionFactory' => $this->categoryCollectionFactoryMock, + 'arrayManager' => $this->arrayManagerMock, + 'authorization' => $this->authorizationMock + ] + ); } public function testModifyData() @@ -130,7 +145,9 @@ public function testModifyMetaLocked($locked) ], ], ]; - + $this->authorizationMock->expects($this->exactly(2)) + ->method('isAllowed') + ->willReturn(true); $this->arrayManagerMock->expects($this->any()) ->method('findPath') ->willReturn('path'); diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index 91a09c907de65..e4c8414ce07b4 100755 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -266,15 +266,17 @@ protected function setUp() $this->searchResultsMock = $this->getMockBuilder(SearchResultsInterface::class) ->getMockForAbstractClass(); $this->eavAttributeMock = $this->getMockBuilder(Attribute::class) - ->setMethods([ - 'load', - 'getAttributeGroupCode', - 'getApplyTo', - 'getFrontendInput', - 'getAttributeCode', - 'usesSource', - 'getSource', - ]) + ->setMethods( + [ + 'load', + 'getAttributeGroupCode', + 'getApplyTo', + 'getFrontendInput', + 'getAttributeCode', + 'usesSource', + 'getSource', + ] + ) ->disableOriginalConstructor() ->getMock(); $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class) @@ -307,9 +309,7 @@ protected function setUp() ->willReturnSelf(); $this->groupCollectionMock->expects($this->any()) ->method('getIterator') - ->willReturn(new \ArrayIterator([ - $this->groupMock, - ])); + ->willReturn(new \ArrayIterator([$this->groupMock])); $this->attributeCollectionMock->expects($this->any()) ->method('addFieldToSelect') ->willReturnSelf(); @@ -324,9 +324,7 @@ protected function setUp() ->willReturn($this->attributeCollectionMock); $this->productMock->expects($this->any()) ->method('getAttributes') - ->willReturn([ - $this->attributeMock, - ]); + ->willReturn([$this->attributeMock,]); $this->storeMock = $this->getMockBuilder(StoreInterface::class) ->setMethods(['load', 'getId', 'getConfig', 'getBaseCurrencyCode']) ->getMockForAbstractClass(); @@ -355,24 +353,27 @@ protected function setUp() */ protected function createModel() { - return $this->objectManager->getObject(Eav::class, [ - 'locator' => $this->locatorMock, - 'eavValidationRules' => $this->eavValidationRulesMock, - 'eavConfig' => $this->eavConfigMock, - 'request' => $this->requestMock, - 'groupCollectionFactory' => $this->groupCollectionFactoryMock, - 'storeManager' => $this->storeManagerMock, - 'formElementMapper' => $this->formElementMapperMock, - 'metaPropertiesMapper' => $this->metaPropertiesMapperMock, - 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, - 'attributeGroupRepository' => $this->attributeGroupRepositoryMock, - 'sortOrderBuilder' => $this->sortOrderBuilderMock, - 'attributeRepository' => $this->attributeRepositoryMock, - 'arrayManager' => $this->arrayManagerMock, - 'eavAttributeFactory' => $this->eavAttributeFactoryMock, - '_eventManager' => $this->eventManagerMock, - 'attributeCollectionFactory' => $this->attributeCollectionFactoryMock - ]); + return $this->objectManager->getObject( + Eav::class, + [ + 'locator' => $this->locatorMock, + 'eavValidationRules' => $this->eavValidationRulesMock, + 'eavConfig' => $this->eavConfigMock, + 'request' => $this->requestMock, + 'groupCollectionFactory' => $this->groupCollectionFactoryMock, + 'storeManager' => $this->storeManagerMock, + 'formElementMapper' => $this->formElementMapperMock, + 'metaPropertiesMapper' => $this->metaPropertiesMapperMock, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, + 'attributeGroupRepository' => $this->attributeGroupRepositoryMock, + 'sortOrderBuilder' => $this->sortOrderBuilderMock, + 'attributeRepository' => $this->attributeRepositoryMock, + 'arrayManager' => $this->arrayManagerMock, + 'eavAttributeFactory' => $this->eavAttributeFactoryMock, + '_eventManager' => $this->eventManagerMock, + 'attributeCollectionFactory' => $this->attributeCollectionFactoryMock + ] + ); } public function testModifyData() @@ -389,9 +390,7 @@ public function testModifyData() ->willReturn($this->attributeCollectionMock); $this->attributeCollectionMock->expects($this->any())->method('getItems') - ->willReturn([ - $this->eavAttributeMock - ]); + ->willReturn([$this->eavAttributeMock]); $this->locatorMock->expects($this->any())->method('getProduct') ->willReturn($this->productMock); @@ -563,6 +562,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_not_new_locked_and_required' => [ @@ -582,6 +582,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], 'locked' => true, ], @@ -602,6 +603,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_new_and_not_required' => [ @@ -621,6 +623,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_new_locked_and_not_required' => [ @@ -640,6 +643,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], 'locked' => true, ], @@ -660,6 +664,7 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, + '__disableTmpl' => ['label' => true, 'code' => true] ], ] ]; diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php index f7554219c6c31..638f225ca1b82 100644 --- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php @@ -65,15 +65,19 @@ public function create($attribute, $context, array $config = []) $filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []); $columnName = $attribute->getAttributeCode(); - $config = array_merge([ - 'label' => __($attribute->getDefaultFrontendLabel()), - 'dataType' => $this->getDataType($attribute), - 'add_field' => true, - 'visible' => $attribute->getIsVisibleInGrid(), - 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers)) - ? $this->getFilterType($attribute->getFrontendInput()) - : null, - ], $config); + $config = array_merge( + [ + 'label' => __($attribute->getDefaultFrontendLabel()), + 'dataType' => $this->getDataType($attribute), + 'add_field' => true, + 'visible' => $attribute->getIsVisibleInGrid(), + 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers)) + ? $this->getFilterType($attribute->getFrontendInput()) + : null, + '__disableTmpl' => ['label' => true], + ], + $config + ); if ($attribute->usesSource()) { $config['options'] = $attribute->getSource()->getAllOptions(); diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php index 0c4efa87c1a32..596b0f4118599 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php @@ -60,6 +60,7 @@ public function prepareDataSource(array $dataSource) ), 'label' => __('Edit'), 'hidden' => false, + '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php b/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php index 894e2b701b5ac..8db1bf8268b66 100644 --- a/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php +++ b/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php @@ -12,6 +12,9 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Component\AbstractComponent; +/** + * Class MassAction + */ class MassAction extends AbstractComponent { const NAME = 'massaction'; @@ -40,7 +43,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function prepare() : void { @@ -49,7 +52,7 @@ public function prepare() : void foreach ($this->getChildComponents() as $actionComponent) { $actionType = $actionComponent->getConfiguration()['type']; if ($this->isActionAllowed($actionType)) { - $config['actions'][] = $actionComponent->getConfiguration(); + $config['actions'][] = array_merge($actionComponent->getConfiguration(), ['__disableTmpl' => true]); } } $origConfig = $this->getConfiguration(); @@ -64,7 +67,7 @@ public function prepare() : void } /** - * {@inheritdoc} + * @inheritdoc */ public function getComponentName() : string { diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php index 249a00a98bff2..8338e898b7637 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AbstractModifier.php @@ -11,6 +11,7 @@ /** * Class AbstractModifier * + * phpcs:disable Magento2.Classes.AbstractApi * @api * * @SuppressWarnings(PHPMD.NumberOfChildren) @@ -132,7 +133,7 @@ private function _getNextAttributeSortOrder(array $meta, $attributeCodes, $defau */ protected function startsWith($haystack, $needle) { - return $needle === '' || strrpos($haystack, $needle, -strlen($haystack)) !== false; + return $needle === '' || strrpos($haystack, (string) $needle, -strlen($haystack)) !== false; } /** diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php index 0733d21bf47d7..53c9595b59e76 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php @@ -78,11 +78,20 @@ public function getOptions() \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection::SORT_ORDER_ASC ); - return $collection->getData(); + $collectionData = $collection->getData() ?? []; + + array_walk( + $collectionData, + function (&$attribute) { + $attribute['__disableTmpl'] = true; + } + ); + + return $collectionData; } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -116,17 +125,20 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyData(array $data) { - return array_replace_recursive($data, [ - $this->locator->getProduct()->getId() => [ - self::DATA_SOURCE_DEFAULT => [ - 'attribute_set_id' => $this->locator->getProduct()->getAttributeSetId() - ], + return array_replace_recursive( + $data, + [ + $this->locator->getProduct()->getId() => [ + self::DATA_SOURCE_DEFAULT => [ + 'attribute_set_id' => $this->locator->getProduct()->getAttributeSetId() + ], + ] ] - ]); + ); } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index 683a96133ad30..a6b9856a4a0ed 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -67,7 +67,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyData(array $data) @@ -76,6 +77,8 @@ public function modifyData(array $data) } /** + * Check if can add attributes on product form. + * * @return boolean */ private function canAddAttributes() @@ -89,7 +92,8 @@ private function canAddAttributes() } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -111,6 +115,8 @@ public function modifyMeta(array $meta) } /** + * Modify meta customize attribute modal. + * * @param array $meta * @return array */ @@ -207,6 +213,8 @@ private function customizeAddAttributeModal(array $meta) } /** + * Modify meta to customize create attribute modal. + * * @param array $meta * @return array */ @@ -289,6 +297,8 @@ private function customizeCreateAttributeModal(array $meta) } /** + * Modify meta to customize attribute grid. + * * @param array $meta * @return array */ @@ -309,7 +319,7 @@ private function customizeAttributesGrid(array $meta) 'immediateUpdateBySelection' => true, 'behaviourType' => 'edit', 'externalFilterMode' => true, - 'dataLinks' => ['imports' => false, 'exports' => true], + 'dataLinks' => ['imports' => false, 'exports' => false], 'formProvider' => 'ns = ${ $.namespace }, index = product_form', 'groupCode' => static::GROUP_CODE, 'groupName' => static::GROUP_NAME, diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index a789ef859d2ce..bc7e484ff5b30 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -17,6 +17,7 @@ use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\UrlInterface; use Magento\Framework\Stdlib\ArrayManager; +use Magento\Framework\AuthorizationInterface; /** * Data provider for categories field of product page @@ -81,6 +82,11 @@ class Categories extends AbstractModifier */ private $serializer; + /** + * @var AuthorizationInterface + */ + private $authorization; + /** * @param LocatorInterface $locator * @param CategoryCollectionFactory $categoryCollectionFactory @@ -88,6 +94,7 @@ class Categories extends AbstractModifier * @param UrlInterface $urlBuilder * @param ArrayManager $arrayManager * @param SerializerInterface $serializer + * @param AuthorizationInterface $authorization */ public function __construct( LocatorInterface $locator, @@ -95,7 +102,8 @@ public function __construct( DbHelper $dbHelper, UrlInterface $urlBuilder, ArrayManager $arrayManager, - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + AuthorizationInterface $authorization = null ) { $this->locator = $locator; $this->categoryCollectionFactory = $categoryCollectionFactory; @@ -103,6 +111,7 @@ public function __construct( $this->urlBuilder = $urlBuilder; $this->arrayManager = $arrayManager; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); + $this->authorization = $authorization ?: ObjectManager::getInstance()->get(AuthorizationInterface::class); } /** @@ -126,12 +135,24 @@ private function getCacheManager() */ public function modifyMeta(array $meta) { - $meta = $this->createNewCategoryModal($meta); + if ($this->isAllowed()) { + $meta = $this->createNewCategoryModal($meta); + } $meta = $this->customizeCategoriesField($meta); return $meta; } + /** + * Check current user permission on category resource + * + * @return bool + */ + private function isAllowed() + { + return $this->authorization->isAllowed('Magento_Catalog::categories'); + } + /** * @inheritdoc * @since 101.0.0 @@ -218,88 +239,91 @@ protected function customizeCategoriesField(array $meta) return $meta; } - $meta = $this->arrayManager->merge( - $containerPath, - $meta, - [ - 'arguments' => [ - 'data' => [ - 'config' => [ - 'label' => __('Categories'), - 'dataScope' => '', - 'breakLine' => false, - 'formElement' => 'container', - 'componentType' => 'container', - 'component' => 'Magento_Ui/js/form/components/group', - 'scopeLabel' => __('[GLOBAL]'), - 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), - ], + $value = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Categories'), + 'dataScope' => '', + 'breakLine' => false, + 'formElement' => 'container', + 'componentType' => 'container', + 'component' => 'Magento_Ui/js/form/components/group', + 'scopeLabel' => __('[GLOBAL]'), + 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), ], ], - 'children' => [ - $fieldCode => [ - 'arguments' => [ - 'data' => [ + ], + 'children' => [ + $fieldCode => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'formElement' => 'select', + 'componentType' => 'field', + 'component' => 'Magento_Catalog/js/components/new-category', + 'filterOptions' => true, + 'chipsEnabled' => true, + 'disableLabel' => true, + 'levelsVisibility' => '1', + 'elementTmpl' => 'ui/grid/filters/elements/ui-select', + 'options' => $this->getCategoriesTree(), + 'listens' => [ + 'index=create_category:responseData' => 'setParsed', + 'newOption' => 'toggleOptionSelected' + ], 'config' => [ - 'formElement' => 'select', - 'componentType' => 'field', - 'component' => 'Magento_Catalog/js/components/new-category', - 'filterOptions' => true, - 'chipsEnabled' => true, - 'disableLabel' => true, - 'levelsVisibility' => '1', - 'elementTmpl' => 'ui/grid/filters/elements/ui-select', - 'options' => $this->getCategoriesTree(), - 'listens' => [ - 'index=create_category:responseData' => 'setParsed', - 'newOption' => 'toggleOptionSelected' - ], - 'config' => [ - 'dataScope' => $fieldCode, - 'sortOrder' => 10, - ], + 'dataScope' => $fieldCode, + 'sortOrder' => 10, ], ], ], ], - 'create_category_button' => [ - 'arguments' => [ - 'data' => [ - 'config' => [ - 'title' => __('New Category'), - 'formElement' => 'container', - 'additionalClasses' => 'admin__field-small', - 'componentType' => 'container', - 'component' => 'Magento_Ui/js/form/components/button', - 'template' => 'ui/form/components/button/container', - 'actions' => [ - [ - 'targetName' => 'product_form.product_form.create_category_modal', - 'actionName' => 'toggleModal', - ], - [ - 'targetName' => - 'product_form.product_form.create_category_modal.create_category', - 'actionName' => 'render' - ], - [ - 'targetName' => - 'product_form.product_form.create_category_modal.create_category', - 'actionName' => 'resetForm' - ] - ], - 'additionalForGroup' => true, - 'provider' => false, - 'source' => 'product_details', - 'displayArea' => 'insideGroup', - 'sortOrder' => 20, - 'dataScope' => $fieldCode, + ], + ] + ]; + if ($this->isAllowed()) { + $value['children']['create_category_button'] = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'title' => __('New Category'), + 'formElement' => 'container', + 'additionalClasses' => 'admin__field-small', + 'componentType' => 'container', + 'component' => 'Magento_Ui/js/form/components/button', + 'template' => 'ui/form/components/button/container', + 'actions' => [ + [ + 'targetName' => 'product_form.product_form.create_category_modal', + 'actionName' => 'toggleModal', + ], + [ + 'targetName' => + 'product_form.product_form.create_category_modal.create_category', + 'actionName' => 'render' ], + [ + 'targetName' => + 'product_form.product_form.create_category_modal.create_category', + 'actionName' => 'resetForm' + ] ], - ] - ] + 'additionalForGroup' => true, + 'provider' => false, + 'source' => 'product_details', + 'displayArea' => 'insideGroup', + 'sortOrder' => 20, + 'dataScope' => $fieldCode, + ], + ], ] - ] + ]; + } + $meta = $this->arrayManager->merge( + $containerPath, + $meta, + $value ); return $meta; diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 6f60a114737b0..7ccc678c1ef01 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -660,6 +660,7 @@ private function isProductExists() * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @api * @since 101.0.0 */ @@ -667,20 +668,25 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC { $configPath = ltrim(static::META_CONFIG_PATH, ArrayManager::DEFAULT_PATH_DELIMITER); $attributeCode = $attribute->getAttributeCode(); - $meta = $this->arrayManager->set($configPath, [], [ - 'dataType' => $attribute->getFrontendInput(), - 'formElement' => $this->getFormElementsMapValue($attribute->getFrontendInput()), - 'visible' => $attribute->getIsVisible(), - 'required' => $attribute->getIsRequired(), - 'notice' => $attribute->getNote() === null ? null : __($attribute->getNote()), - 'default' => (!$this->isProductExists()) ? $this->getAttributeDefaultValue($attribute) : null, - 'label' => __($attribute->getDefaultFrontendLabel()), - 'code' => $attributeCode, - 'source' => $groupCode, - 'scopeLabel' => $this->getScopeLabel($attribute), - 'globalScope' => $this->isScopeGlobal($attribute), - 'sortOrder' => $sortOrder * self::SORT_ORDER_MULTIPLIER, - ]); + $meta = $this->arrayManager->set( + $configPath, + [], + [ + 'dataType' => $attribute->getFrontendInput(), + 'formElement' => $this->getFormElementsMapValue($attribute->getFrontendInput()), + 'visible' => $attribute->getIsVisible(), + 'required' => $attribute->getIsRequired(), + 'notice' => $attribute->getNote() === null ? null : __($attribute->getNote()), + 'default' => (!$this->isProductExists()) ? $this->getAttributeDefaultValue($attribute) : null, + 'label' => __($attribute->getDefaultFrontendLabel()), + 'code' => $attributeCode, + 'source' => $groupCode, + 'scopeLabel' => $this->getScopeLabel($attribute), + 'globalScope' => $this->isScopeGlobal($attribute), + 'sortOrder' => $sortOrder * self::SORT_ORDER_MULTIPLIER, + '__disableTmpl' => ['label' => true, 'code' => true] + ] + ); // TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done $attributeModel = $this->getAttributeModel($attribute); @@ -689,39 +695,39 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC foreach ($options as &$option) { $option['__disableTmpl'] = true; } - $meta = $this->arrayManager->merge($configPath, $meta, [ - 'options' => $this->convertOptionsValueToString($options), - ]); + $meta = $this->arrayManager->merge( + $configPath, + $meta, + ['options' => $this->convertOptionsValueToString($options)] + ); } if ($this->canDisplayUseDefault($attribute)) { - $meta = $this->arrayManager->merge($configPath, $meta, [ - 'service' => [ - 'template' => 'ui/form/element/helper/service', + $meta = $this->arrayManager->merge( + $configPath, + $meta, + [ + 'service' => [ + 'template' => 'ui/form/element/helper/service', + ] ] - ]); + ); } if (!$this->arrayManager->exists($configPath . '/componentType', $meta)) { - $meta = $this->arrayManager->merge($configPath, $meta, [ - 'componentType' => Field::NAME, - ]); + $meta = $this->arrayManager->merge($configPath, $meta, ['componentType' => Field::NAME]); } $product = $this->locator->getProduct(); if (in_array($attributeCode, $this->attributesToDisable) || $product->isLockedAttribute($attributeCode)) { - $meta = $this->arrayManager->merge($configPath, $meta, [ - 'disabled' => true, - ]); + $meta = $this->arrayManager->merge($configPath, $meta, ['disabled' => true]); } // TODO: getAttributeModel() should not be used when MAGETWO-48284 is complete $childData = $this->arrayManager->get($configPath, $meta, []); if (($rules = $this->catalogEavValidationRules->build($this->getAttributeModel($attribute), $childData))) { - $meta = $this->arrayManager->merge($configPath, $meta, [ - 'validation' => $rules, - ]); + $meta = $this->arrayManager->merge($configPath, $meta, ['validation' => $rules]); } $meta = $this->addUseDefaultValueCheckbox($attribute, $meta); @@ -789,11 +795,14 @@ private function getAttributeDefaultValue(ProductAttributeInterface $attribute) */ private function convertOptionsValueToString(array $options) : array { - array_walk($options, function (&$value) { - if (isset($value['value']) && is_scalar($value['value'])) { - $value['value'] = (string)$value['value']; + array_walk( + $options, + function (&$value) { + if (isset($value['value']) && is_scalar($value['value'])) { + $value['value'] = (string)$value['value']; + } } - }); + ); return $options; } @@ -842,6 +851,7 @@ public function setupAttributeContainerMeta(ProductAttributeInterface $attribute 'breakLine' => false, 'label' => $attribute->getDefaultFrontendLabel(), 'required' => $attribute->getIsRequired(), + '__disableTmpl' => ['label' => true] ] ); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index 9cbbb86a2c555..b9d8fc56a91d9 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -165,7 +165,7 @@ protected function getFieldsForFieldset() $websitesList = $this->getWebsitesList(); $isNewProduct = !$this->locator->getProduct()->getId(); $tooltip = [ - 'link' => 'http://docs.magento.com/m2/ce/user_guide/configuration/scope.html', + 'link' => 'https://docs.magento.com/m2/ce/user_guide/configuration/scope.html', 'description' => __( 'If your Magento installation has multiple websites, ' . 'you can edit the scope to use the product on specific sites.' @@ -175,11 +175,9 @@ protected function getFieldsForFieldset() $label = __('Websites'); $defaultWebsiteId = $this->websiteRepository->getDefault()->getId(); - $isOnlyOneWebsiteAvailable = count($websitesList) === 1; foreach ($websitesList as $website) { $isChecked = in_array($website['id'], $websiteIds) - || ($defaultWebsiteId == $website['id'] && $isNewProduct) - || $isOnlyOneWebsiteAvailable; + || ($defaultWebsiteId == $website['id'] && $isNewProduct); $children[$website['id']] = [ 'arguments' => [ 'data' => [ diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php index e5451c8e49847..ea8fc6f2d83b2 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php @@ -84,8 +84,11 @@ private function addAttributeToFilterAllStores(Attribute $attributeModel, array $entity = $this->getEntity(); $fKey = 'e.' . $this->getEntityPkName($entity); $pKey = $tableName . '.' . $this->getEntityPkName($entity); + $attributeId = $attributeModel->getAttributeId(); $condition = "({$pKey} = {$fKey}) AND (" . $this->_getConditionSql("{$tableName}.value", $condition) + . ') AND (' + . $this->_getConditionSql("{$tableName}.attribute_id", $attributeId) . ')'; $selectExistsInAllStores = $this->getConnection()->select()->from($tableName); $this->getSelect()->exists($selectExistsInAllStores, $condition); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorComposite.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorComposite.php index 359b1a1a948f8..7edf25ff20cc1 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorComposite.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorComposite.php @@ -9,8 +9,7 @@ use Magento\Catalog\Api\Data\ProductRenderInterface; /** - * Composite, which holds collectors, that collect enought information for - * product render + * Composite, which holds collectors, that collect enough information for product render */ class ProductRenderCollectorComposite implements ProductRenderCollectorInterface { diff --git a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php index 27829155af292..00bac7e61b5b4 100644 --- a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php +++ b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php @@ -10,6 +10,7 @@ use Magento\Framework\View\Element\Block\ArgumentInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; /** * Check is available add to compare. @@ -37,7 +38,11 @@ public function __construct(StockConfigurationInterface $stockConfiguration) */ public function isAvailableForCompare(ProductInterface $product): bool { - return $this->isInStock($product) || $this->stockConfiguration->isShowOutOfStock(); + if ((int)$product->getStatus() !== Status::STATUS_DISABLED) { + return $this->isInStock($product) || $this->stockConfiguration->isShowOutOfStock(); + } + + return false; } /** @@ -53,6 +58,6 @@ private function isInStock(ProductInterface $product): bool return $product->isSalable(); } - return isset($quantityAndStockStatus['is_in_stock']) && $quantityAndStockStatus['is_in_stock']; + return $quantityAndStockStatus['is_in_stock'] ?? false; } } diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 1057367beb447..281a19ac27805 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-authorization": "100.3.*", "magento/module-asynchronous-operations": "100.3.*", @@ -51,5 +51,5 @@ "Magento\\Catalog\\": "" } }, - "version": "103.0.2" + "version": "103.0.3" } diff --git a/app/code/Magento/Catalog/etc/acl.xml b/app/code/Magento/Catalog/etc/acl.xml index fbc61141258a0..c7c0f1f75872d 100644 --- a/app/code/Magento/Catalog/etc/acl.xml +++ b/app/code/Magento/Catalog/etc/acl.xml @@ -12,7 +12,8 @@ <resource id="Magento_Catalog::catalog" title="Catalog" translate="title" sortOrder="30"> <resource id="Magento_Catalog::catalog_inventory" title="Inventory" translate="title" sortOrder="10"> <resource id="Magento_Catalog::products" title="Products" translate="title" sortOrder="10"> - <resource id="Magento_Catalog::edit_product_design" title="Edit Product Design" translate="title" /> + <resource id="Magento_Catalog::update_attributes" title="Update Attributes" translate="title" sortOrder="10" /> + <resource id="Magento_Catalog::edit_product_design" title="Edit Product Design" translate="title" sortOrder="20" /> </resource> <resource id="Magento_Catalog::categories" title="Categories" translate="title" sortOrder="20"> <resource id="Magento_Catalog::edit_category_design" title="Edit Category Design" translate="title" /> @@ -27,7 +28,6 @@ </resource> <resource id="Magento_Backend::stores_attributes"> <resource id="Magento_Catalog::attributes_attributes" title="Product" translate="title" sortOrder="30" /> - <resource id="Magento_Catalog::update_attributes" title="Update Attributes" translate="title" sortOrder="35" /> <resource id="Magento_Catalog::sets" title="Attribute Set" translate="title" sortOrder="40"/> </resource> </resource> diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 1d563244f1432..a6dd6cbd2e9a1 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -114,14 +114,14 @@ </group> <group id="seo" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Search Engine Optimization</label> - <field id="title_separator" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="title_separator" translate="label" type="text" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Page Title Separator</label> </field> - <field id="category_canonical_tag" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="category_canonical_tag" translate="label" type="select" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Use Canonical Link Meta Tag For Categories</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="product_canonical_tag" translate="label" type="select" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="product_canonical_tag" translate="label" type="select" sortOrder="9" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Use Canonical Link Meta Tag For Products</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Catalog/etc/crontab.xml b/app/code/Magento/Catalog/etc/crontab.xml index c48f1307a09f5..74c60323530e1 100644 --- a/app/code/Magento/Catalog/etc/crontab.xml +++ b/app/code/Magento/Catalog/etc/crontab.xml @@ -16,7 +16,7 @@ <job name="catalog_product_outdated_price_values_cleanup" instance="Magento\Catalog\Cron\DeleteOutdatedPriceValues" method="execute"> <schedule>* * * * *</schedule> </job> - <job name="catalog_product_frontend_actions_flush" instance="Magento\Catalog\Cron\DeleteOutdatedPriceValues" method="execute"> + <job name="catalog_product_frontend_actions_flush" instance="Magento\Catalog\Cron\FrontendActionsFlush" method="execute"> <schedule>* * * * *</schedule> </job> <job name="catalog_product_attribute_value_synchronize" instance="Magento\Catalog\Cron\SynchronizeWebsiteAttributes" method="execute"> diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index 17e3dddc41c3b..b5e02b1daaa01 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -77,7 +77,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="value" scale="6" precision="20" unsigned="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> @@ -325,7 +325,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="value" scale="6" precision="20" unsigned="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> @@ -658,7 +658,7 @@ identity="false" comment="Product Link Attribute ID"/> <column xsi:type="int" name="link_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Link ID"/> - <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="value" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> @@ -745,7 +745,7 @@ default="0" comment="Customer Group ID"/> <column xsi:type="decimal" name="qty" scale="4" precision="12" unsigned="false" nullable="false" default="1" comment="QTY"/> - <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="value" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Value"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> @@ -877,7 +877,7 @@ default="0" comment="Option ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Price"/> <column xsi:type="varchar" name="price_type" nullable="false" length="7" default="fixed" comment="Price Type"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -950,7 +950,7 @@ default="0" comment="Option Type ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Price"/> <column xsi:type="varchar" name="price_type" nullable="false" length="7" default="fixed" comment="Price Type"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -1142,15 +1142,15 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="final_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="final_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Final Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1177,7 +1177,7 @@ comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1227,9 +1227,9 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="parent_id"/> @@ -1238,7 +1238,7 @@ <column name="website_id"/> </constraint> </table> - <table name="catalog_product_index_price_cfg_opt_agr_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_cfg_opt_agr_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Config Option Aggregate Temp Table"> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent ID"/> @@ -1248,9 +1248,9 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="parent_id"/> @@ -1267,11 +1267,11 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1279,7 +1279,7 @@ <column name="website_id"/> </constraint> </table> - <table name="catalog_product_index_price_cfg_opt_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_cfg_opt_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Config Option Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1287,11 +1287,11 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1309,17 +1309,17 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="orig_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="orig_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Original Price"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="base_tier" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tier" scale="6" precision="20" unsigned="false" nullable="true" comment="Base Tier"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1327,7 +1327,7 @@ <column name="website_id"/> </constraint> </table> - <table name="catalog_product_index_price_final_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_final_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Final Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1337,17 +1337,17 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="orig_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="orig_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Original Price"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> - <column xsi:type="decimal" name="base_tier" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tier" scale="6" precision="20" unsigned="false" nullable="true" comment="Base Tier"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1363,11 +1363,11 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1375,7 +1375,7 @@ <column name="website_id"/> </constraint> </table> - <table name="catalog_product_index_price_opt_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_opt_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Option Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1383,11 +1383,11 @@ default="0" comment="Customer Group ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1405,11 +1405,11 @@ comment="Website ID"/> <column xsi:type="int" name="option_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Option ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1418,7 +1418,7 @@ <column name="option_id"/> </constraint> </table> - <table name="catalog_product_index_price_opt_agr_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_opt_agr_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Option Aggregate Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1428,11 +1428,11 @@ comment="Website ID"/> <column xsi:type="int" name="option_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Option ID"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1470,7 +1470,7 @@ <column name="value"/> </index> </table> - <table name="catalog_product_index_eav_tmp" resource="default" engine="memory" + <table name="catalog_product_index_eav_tmp" resource="default" engine="innodb" comment="Catalog Product EAV Indexer Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1489,13 +1489,13 @@ <column name="value"/> <column name="source_id"/> </constraint> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_ATTRIBUTE_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_ATTRIBUTE_ID" indexType="btree"> <column name="attribute_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_STORE_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_STORE_ID" indexType="btree"> <column name="store_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_VALUE" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_TMP_VALUE" indexType="btree"> <column name="value"/> </index> </table> @@ -1528,7 +1528,7 @@ <column name="value"/> </index> </table> - <table name="catalog_product_index_eav_decimal_tmp" resource="default" engine="memory" + <table name="catalog_product_index_eav_decimal_tmp" resource="default" engine="innodb" comment="Catalog Product EAV Decimal Indexer Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1547,13 +1547,13 @@ <column name="value"/> <column name="source_id"/> </constraint> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_ATTRIBUTE_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_ATTRIBUTE_ID" indexType="btree"> <column name="attribute_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_STORE_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_STORE_ID" indexType="btree"> <column name="store_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_VALUE" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_VALUE" indexType="btree"> <column name="value"/> </index> </table> @@ -1567,15 +1567,15 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="final_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="final_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Final Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1592,7 +1592,7 @@ <column name="min_price"/> </index> </table> - <table name="catalog_product_index_price_tmp" resource="default" engine="memory" + <table name="catalog_product_index_price_tmp" resource="default" engine="innodb" comment="Catalog Product Price Indexer Temp Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/> @@ -1602,32 +1602,32 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="final_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="final_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Final Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> <column name="customer_group_id"/> <column name="website_id"/> </constraint> - <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_CUSTOMER_GROUP_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_CUSTOMER_GROUP_ID" indexType="btree"> <column name="customer_group_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_WEBSITE_ID" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_WEBSITE_ID" indexType="btree"> <column name="website_id"/> </index> - <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE" indexType="hash"> + <index referenceId="CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE" indexType="btree"> <column name="min_price"/> </index> </table> - <table name="catalog_category_product_index_tmp" resource="default" engine="memory" + <table name="catalog_category_product_index_tmp" resource="default" engine="innodb" comment="Catalog Category Product Indexer temporary table"> <column xsi:type="int" name="category_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Category ID"/> @@ -1646,7 +1646,7 @@ <column name="product_id"/> <column name="store_id"/> </constraint> - <index referenceId="CAT_CTGR_PRD_IDX_TMP_PRD_ID_CTGR_ID_STORE_ID" indexType="hash"> + <index referenceId="CAT_CTGR_PRD_IDX_TMP_PRD_ID_CTGR_ID_STORE_ID" indexType="btree"> <column name="product_id"/> <column name="category_id"/> <column name="store_id"/> @@ -1738,15 +1738,15 @@ comment="Website ID"/> <column xsi:type="smallint" name="tax_class_id" padding="5" unsigned="true" nullable="true" identity="false" default="0" comment="Tax Class ID"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="6" precision="20" unsigned="false" nullable="true" comment="Price"/> - <column xsi:type="decimal" name="final_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="final_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Final Price"/> - <column xsi:type="decimal" name="min_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="min_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Min Price"/> - <column xsi:type="decimal" name="max_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="max_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Max Price"/> - <column xsi:type="decimal" name="tier_price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tier_price" scale="6" precision="20" unsigned="false" nullable="true" comment="Tier Price"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 49447447622f9..1c0b2daf4d6f3 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -377,11 +377,11 @@ </argument> </arguments> </virtualType> - <virtualType name="Magento\Catalog\Pricing\Price\Collection" type="Magento\Framework\Pricing\Price\Collection"> + <type name="Magento\Catalog\Pricing\Price\Collection"> <arguments> <argument name="pool" xsi:type="object">Magento\Catalog\Pricing\Price\Pool</argument> </arguments> - </virtualType> + </type> <type name="Magento\Framework\Pricing\PriceInfo\Factory"> <arguments> <argument name="types" xsi:type="array"> @@ -1165,4 +1165,12 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product\Option\Type\Select"> + <arguments> + <argument name="singleSelectionTypes" xsi:type="array"> + <item name="drop_down" xsi:type="const">Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN</item> + <item name="radio" xsi:type="const">Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml index b42a78dfbe302..5d57c2d8f861a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_grid.xml @@ -8,6 +8,8 @@ <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <update handle="formkey"/> <container name="root" label="Root"> - <block class="Magento\Catalog\Block\Adminhtml\Product\Grid" name="admin.product.grid"/> + <block class="Magento\Catalog\Block\Adminhtml\Product\Grid" name="admin.product.grid"> + <block class="Magento\Framework\View\Element\Text\ListText" name="grid.bottom.links"/> + </block> </container> </layout> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml index ee67acd0ebd46..cea54e883d2aa 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** * @var $block \Magento\Catalog\Block\Adminhtml\Category\Tree */ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit.phtml index f58b39a819a0c..c77b66733afc4 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit.phtml @@ -5,18 +5,18 @@ */ /** - * Template for \Magento\Catalog\Block\Adminhtml\Category\Edit + * @var $block \Magento\Catalog\Block\Adminhtml\Category\Edit */ ?> <div data-id="information-dialog-category" class="messages" style="display: none;"> <div class="message message-notice"> - <div><?= /* @escapeNotVerified */ __('This operation can take a long time') ?></div> + <div><?= $block->escapeHtml(__('This operation can take a long time')) ?></div> </div> </div> <script type="text/x-magento-init"> { "*": { - "categoryForm": {"refreshUrl": "<?= /* @escapeNotVerified */ $block->getRefreshPathUrl() ?>"} + "categoryForm": {"refreshUrl": "<?= $block->escapeJs($block->escapeUrl($block->getRefreshPathUrl())) ?>"} } } </script> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit/assign_products.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit/assign_products.phtml index 4691a709cadeb..af7aec12a57ed 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit/assign_products.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/edit/assign_products.phtml @@ -16,8 +16,8 @@ $gridJsObjectName = $blockGrid->getJsObjectName(); { "*": { "Magento_Catalog/catalog/category/assign-products": { - "selectedProducts": <?= /* @escapeNotVerified */ $block->getProductsJson() ?>, - "gridJsObjectName": <?= /* @escapeNotVerified */ '"' . $gridJsObjectName . '"' ?: '{}' ?> + "selectedProducts": <?= /* @noEscape */ $block->getProductsJson() ?>, + "gridJsObjectName": <?= /* @noEscape */ '"' . $gridJsObjectName . '"' ?: '{}' ?> } } } diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml index f448edc692ce2..6b63a20134df1 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml @@ -4,27 +4,26 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Adminhtml\Category\Tree */ ?> <div class="categories-side-col"> <div class="sidebar-actions"> - <?php if ($block->getRoot()): ?> + <?php if ($block->getRoot()) :?> <?= $block->getAddRootButtonHtml() ?><br/> <?= $block->getAddSubButtonHtml() ?> <?php endif; ?> </div> <div class="tree-actions"> - <?php if ($block->getRoot()): ?> + <?php if ($block->getRoot()) :?> <?php //echo $block->getCollapseButtonHtml() ?> <?php //echo $block->getExpandButtonHtml() ?> <a href="#" - onclick="tree.collapseTree(); return false;"><?= /* @escapeNotVerified */ __('Collapse All') ?></a> + onclick="tree.collapseTree(); return false;"><?= $block->escapeHtml(__('Collapse All')) ?></a> <span class="separator">|</span> <a href="#" - onclick="tree.expandTree(); return false;"><?= /* @escapeNotVerified */ __('Expand All') ?></a> + onclick="tree.expandTree(); return false;"><?= $block->escapeHtml(__('Expand All')) ?></a> <?php endif; ?> </div> - <?php if ($block->getRoot()): ?> + <?php if ($block->getRoot()) :?> <div class="tree-holder"> <div id="tree-div" class="tree-wrapper"></div> </div> @@ -32,7 +31,7 @@ <div data-id="information-dialog-tree" class="messages" style="display: none;"> <div class="message message-notice"> - <div><?= /* @escapeNotVerified */ __('This operation can take a long time') ?></div> + <div><?= $block->escapeHtml(__('This operation can take a long time')) ?></div> </div> </div> <script> @@ -172,7 +171,7 @@ if (!this.collapsed) { this.collapsed = true; - this.loader.dataUrl = '<?= /* @escapeNotVerified */ $block->getLoadTreeUrl(false) ?>'; + this.loader.dataUrl = '<?= $block->escapeJs($block->escapeUrl($block->getLoadTreeUrl(false))) ?>'; this.request(this.loader.dataUrl, false); } }, @@ -181,7 +180,7 @@ this.expandAll(); if (this.collapsed) { this.collapsed = false; - this.loader.dataUrl = '<?= /* @escapeNotVerified */ $block->getLoadTreeUrl(true) ?>'; + this.loader.dataUrl = '<?= $block->escapeJs($block->escapeUrl($block->getLoadTreeUrl(true))) ?>'; this.request(this.loader.dataUrl, false); } }, @@ -216,7 +215,7 @@ if (tree && switcherParams) { var url; if (switcherParams.useConfirm) { - if (!confirm("<?= /* @escapeNotVerified */ __('Please confirm site switching. All data that hasn\'t been saved will be lost.') ?>")) { + if (!confirm("<?= $block->escapeJs(__('Please confirm site switching. All data that hasn\'t been saved will be lost.')) ?>")) { return false; } } @@ -259,7 +258,7 @@ } }); } else { - var baseUrl = '<?= /* @escapeNotVerified */ $block->getEditUrl() ?>'; + var baseUrl = '<?= $block->escapeJs($block->escapeUrl($block->getEditUrl())) ?>'; var urlExt = switcherParams.scopeParams + 'id/' + tree.currentNodeId + '/'; url = parseSidUrl(baseUrl, urlExt); setLocation(url); @@ -296,7 +295,7 @@ if (scopeParams) { url = url + scopeParams; } - <?php if ($block->isClearEdit()): ?> + <?php if ($block->isClearEdit()) :?> if (selectedNode) { url = url + 'id/' + config.parameters.category_id; } @@ -307,7 +306,7 @@ jQuery(function () { categoryLoader = new Ext.tree.TreeLoader({ - dataUrl: '<?= /* @escapeNotVerified */ $block->getLoadTreeUrl() ?>' + dataUrl: '<?= $block->escapeJs($block->escapeUrl($block->getLoadTreeUrl())) ?>' }); categoryLoader.processResponse = function (response, parent, callback) { @@ -389,26 +388,26 @@ enableDD: true, containerScroll: true, selModel: new Ext.tree.CheckNodeMultiSelectionModel(), - rootVisible: '<?= /* @escapeNotVerified */ $block->getRoot()->getIsVisible() ?>', - useAjax: <?= /* @escapeNotVerified */ $block->getUseAjax() ?>, - switchTreeUrl: '<?= /* @escapeNotVerified */ $block->getSwitchTreeUrl() ?>', - editUrl: '<?= /* @escapeNotVerified */ $block->getEditUrl() ?>', - currentNodeId: <?= /* @escapeNotVerified */ (int)$block->getCategoryId() ?>, - baseUrl: '<?= /* @escapeNotVerified */ $block->getEditUrl() ?>' + rootVisible: '<?= (bool)$block->getRoot()->getIsVisible() ?>', + useAjax: <?= $block->escapeJs($block->getUseAjax()) ?>, + switchTreeUrl: '<?= $block->escapeJs($block->escapeUrl($block->getSwitchTreeUrl())) ?>', + editUrl: '<?= $block->escapeJs($block->escapeUrl($block->getEditUrl())) ?>', + currentNodeId: <?= (int)$block->getCategoryId() ?>, + baseUrl: '<?= $block->escapeJs($block->escapeUrl($block->getEditUrl())) ?>' }; defaultLoadTreeParams = { parameters: { - text: <?= /* @escapeNotVerified */ json_encode(htmlentities($block->getRoot()->getName())) ?>, + text: <?= /* @noEscape */ json_encode(htmlentities($block->getRoot()->getName())) ?>, draggable: false, - allowDrop: <?php if ($block->getRoot()->getIsVisible()): ?>true<?php else : ?>false<?php endif; ?>, + allowDrop: <?php if ($block->getRoot()->getIsVisible()) :?>true<?php else :?>false<?php endif; ?>, id: <?= (int)$block->getRoot()->getId() ?>, expanded: <?= (int)$block->getIsWasExpanded() ?>, store_id: <?= (int)$block->getStore()->getId() ?>, category_id: <?= (int)$block->getCategoryId() ?>, parent: <?= (int)$block->getRequest()->getParam('parent') ?> }, - data: <?= /* @escapeNotVerified */ $block->getTreeJson() ?> + data: <?= /* @noEscape */ $block->getTreeJson() ?> }; reRenderTree(); @@ -486,7 +485,7 @@ click: function () { (function ($) { $.ajax({ - url: '<?= /* @escapeNotVerified */ $block->getMoveUrl() ?>', + url: '<?= $block->escapeJs($block->escapeUrl($block->getMoveUrl())) ?>', method: 'POST', data: registry.get('pd'), showLoader: true diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml index 69737b8a37c1c..e24d676974b01 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml @@ -3,24 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_divId = 'tree' . $block->getId() ?> -<div id="<?= /* @escapeNotVerified */ $_divId ?>" class="tree"></div> +<div id="<?= $block->escapeHtmlAttr($_divId) ?>" class="tree"></div> <!--[if IE]> <script id="ie-deferred-loader" defer="defer" src="//:"></script> <![endif]--> <script> require(['jquery', "prototype", "extjs/ext-tree-checkbox"], function(jQuery){ -var tree<?= /* @escapeNotVerified */ $block->getId() ?>; +var tree<?= $block->escapeJs($block->getId()) ?>; -var useMassaction = <?= /* @escapeNotVerified */ $block->getUseMassaction() ? 1 : 0 ?>; +var useMassaction = <?= $block->getUseMassaction() ? 1 : 0 ?>; -var isAnchorOnly = <?= /* @escapeNotVerified */ $block->getIsAnchorOnly() ? 1 : 0 ?>; +var isAnchorOnly = <?= $block->getIsAnchorOnly() ? 1 : 0 ?>; Ext.tree.TreePanel.Enhanced = function(el, config) { @@ -44,8 +41,8 @@ Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, { this.setRootNode(root); if (firstLoad) { - <?php if ($block->getNodeClickListener()): ?> - this.addListener('click', <?= /* @escapeNotVerified */ $block->getNodeClickListener() ?>.createDelegate(this)); + <?php if ($block->getNodeClickListener()) :?> + this.addListener('click', <?= /* @noEscape */ $block->getNodeClickListener() ?>.createDelegate(this)); <?php endif; ?> } @@ -58,10 +55,10 @@ Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, { jQuery(function() { - var emptyNodeAdded = <?= /* @escapeNotVerified */ ($block->getWithEmptyNode() ? 'false' : 'true') ?>; + var emptyNodeAdded = <?= ($block->getWithEmptyNode() ? 'false' : 'true') ?>; var categoryLoader = new Ext.tree.TreeLoader({ - dataUrl: '<?= /* @escapeNotVerified */ $block->getLoadTreeUrl() ?>' + dataUrl: '<?= $block->escapeJs($block->escapeUrl($block->getLoadTreeUrl())) ?>' }); categoryLoader.buildCategoryTree = function(parent, config) @@ -80,7 +77,7 @@ jQuery(function() // Add empty node to reset category filter if(!emptyNodeAdded) { var empty = Object.clone(_node); - empty.text = '<?= /* @escapeNotVerified */ __('None') ?>'; + empty.text = '<?= $block->escapeJs(__('None')) ?>'; empty.children = []; empty.id = 'none'; empty.path = '1/none'; @@ -151,11 +148,11 @@ jQuery(function() }; categoryLoader.on("beforeload", function(treeLoader, node) { - $('<?= /* @escapeNotVerified */ $_divId ?>').fire('category:beforeLoad', {treeLoader:treeLoader}); + $('<?= $block->escapeJs($_divId) ?>').fire('category:beforeLoad', {treeLoader:treeLoader}); treeLoader.baseParams.id = node.attributes.id; }); - tree<?= /* @escapeNotVerified */ $block->getId() ?> = new Ext.tree.TreePanel.Enhanced('<?= /* @escapeNotVerified */ $_divId ?>', { + tree<?= $block->escapeJs($block->getId()) ?> = new Ext.tree.TreePanel.Enhanced('<?= $block->escapeJs($_divId) ?>', { animate: false, loader: categoryLoader, enableDD: false, @@ -167,9 +164,9 @@ jQuery(function() }); if (useMassaction) { - tree<?= /* @escapeNotVerified */ $block->getId() ?>.on('check', function(node) { - $('<?= /* @escapeNotVerified */ $_divId ?>').fire('node:changed', {node:node}); - }, tree<?= /* @escapeNotVerified */ $block->getId() ?>); + tree<?= $block->escapeJs($block->getId()) ?>.on('check', function(node) { + $('<?= $block->escapeJs($_divId) ?>').fire('node:changed', {node:node}); + }, tree<?= $block->escapeJs($block->getId()) ?>); } // set the root node @@ -181,7 +178,7 @@ jQuery(function() category_id: <?= (int) $block->getCategoryId() ?> }; - tree<?= /* @escapeNotVerified */ $block->getId() ?>.loadTree({parameters:parameters, data:<?= /* @escapeNotVerified */ $block->getTreeJson() ?>},true); + tree<?= $block->escapeJs($block->getId()) ?>.loadTree({parameters:parameters, data:<?= /* @noEscape */ $block->getTreeJson() ?>},true); }); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/form/renderer/fieldset/element.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/form/renderer/fieldset/element.phtml index 680361eae448e..cbda491a64740 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/form/renderer/fieldset/element.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/form/renderer/fieldset/element.phtml @@ -3,19 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php -/** - * @see \Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset\Element - */ +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + +/** @var $block \Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset\Element */ ?> <?php /* @var $block \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element */ $element = $block->getElement(); -$note = $element->getNote() ? '<div class="note admin__field-note">' . $element->getNote() . '</div>' : ''; +$note = $element->getNote() ? '<div class="note admin__field-note">' . $block->escapeHtml($element->getNote()) . '</div>' : ''; $elementBeforeLabel = $element->getExtType() == 'checkbox' || $element->getExtType() == 'radio'; $addOn = $element->getBeforeElementHtml() || $element->getAfterElementHtml(); $fieldId = ($element->getHtmlId()) ? ' id="attribute-' . $element->getHtmlId() . '-container"' : ''; @@ -27,8 +24,8 @@ $fieldClass .= ($element->getRequired()) ? ' required' : ''; $fieldClass .= ($note) ? ' with-note' : ''; $fieldClass .= ($entity && $entity->getIsUserDefined()) ? ' user-defined type-' . $entity->getFrontendInput() : ''; -$fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' - . $block->getUiId('form-field', $element->getId()); +$fieldAttributes = $fieldId . ' class="' . $block->escapeHtmlAttr($fieldClass) . '" ' + . $block->getUiId('form-field', $block->escapeHtmlAttr($element->getId())); ?> <?php $block->checkFieldDisable() ?> @@ -36,38 +33,38 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'toggleValueElements(this, this.parentNode.parentNode.parentNode)'; ?> -<?php if (!$element->getNoDisplay()): ?> - <?php if ($element->getType() == 'hidden'): ?> +<?php if (!$element->getNoDisplay()) :?> + <?php if ($element->getType() == 'hidden') :?> <?= $element->getElementHtml() ?> - <?php else: ?> - <div<?= /* @escapeNotVerified */ $fieldAttributes ?> data-attribute-code="<?= $element->getHtmlId() ?>" - data-apply-to="<?= $block->escapeHtml($this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode( + <?php else :?> + <div<?= /* @noEscape */ $fieldAttributes ?> data-attribute-code="<?= $element->getHtmlId() ?>" + data-apply-to="<?= $block->escapeHtmlAttr($this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode( $element->hasEntityAttribute() ? $element->getEntityAttribute()->getApplyTo() : [] ))?>" > - <?php if ($elementBeforeLabel): ?> + <?php if ($elementBeforeLabel) :?> <?= $block->getElementHtml() ?> <?= $element->getLabelHtml('', $block->getScopeLabel()) ?> - <?= /* @escapeNotVerified */ $note ?> - <?php else: ?> + <?= /* @noEscape */ $note ?> + <?php else :?> <?= $element->getLabelHtml('', $block->getScopeLabel()) ?> <div class="admin__field-control control"> - <?= /* @escapeNotVerified */ ($addOn) ? '<div class="addon">' . $block->getElementHtml() . '</div>' : $block->getElementHtml() ?> - <?= /* @escapeNotVerified */ $note ?> + <?= ($addOn) ? '<div class="addon">' . $block->getElementHtml() . '</div>' : $block->getElementHtml() ?> + <?= /* @noEscape */ $note ?> </div> <?php endif; ?> <div class="field-service"> - <?php if ($block->canDisplayUseDefault()): ?> + <?php if ($block->canDisplayUseDefault()) :?> <label for="<?= $element->getHtmlId() ?>_default" class="choice use-default"> - <input <?php if ($element->getReadonly()):?> disabled="disabled"<?php endif; ?> + <input <?php if ($element->getReadonly()) :?> disabled="disabled"<?php endif; ?> type="checkbox" name="use_default[]" class="use-default-control" id="<?= $element->getHtmlId() ?>_default" - <?php if ($block->usedDefault()): ?> checked="checked"<?php endif; ?> - onclick="<?= /* @escapeNotVerified */ $elementToggleCode ?>" - value="<?= /* @escapeNotVerified */ $block->getAttributeCode() ?>"/> - <span class="use-default-label"><?= /* @escapeNotVerified */ __('Use Default Value') ?></span> + <?php if ($block->usedDefault()) :?> checked="checked"<?php endif; ?> + onclick="<?= $block->escapeHtmlAttr($elementToggleCode) ?>" + value="<?= $block->escapeHtmlAttr($block->getAttributeCode()) ?>"/> + <span class="use-default-label"><?= $block->escapeHtml(__('Use Default Value')) ?></span> </label> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product.phtml index ce4d8450f5e63..9b9fff2cfc344 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml index 124194519b978..e30b981ff36a6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml @@ -4,16 +4,14 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** * @var $block \Magento\Backend\Block\Widget\Form\Container */ ?> -<?= /* @escapeNotVerified */ $block->getFormInitScripts() ?> -<div data-mage-init='{"floatingHeader": {}}' class="page-actions attribute-popup-actions" <?= /* @escapeNotVerified */ $block->getUiId('content-header') ?>> +<?= /* @noEscape */ $block->getFormInitScripts() ?> +<div data-mage-init='{"floatingHeader": {}}' class="page-actions attribute-popup-actions" <?= /* @noEscape */ $block->getUiId('content-header') ?>> <?= $block->getButtonsHtml('header') ?> </div> @@ -25,9 +23,9 @@ { "#edit_form": { "Magento_Catalog/catalog/product/edit/attribute": { - "validationUrl": "<?= /* @escapeNotVerified */ $block->getValidationUrl() ?>" + "validationUrl": "<?= $block->escapeJs($block->escapeUrl($block->getValidationUrl())) ?>" } } } </script> -<?= /* @escapeNotVerified */ $block->getFormScripts() ?> +<?= /* @noEscape */ $block->getFormScripts() ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml index 195ac92422715..f020eddc35dbd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -4,17 +4,17 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <script> require([ "jquery", 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/modal/prompt', + 'uiRegistry', "collapsable", "prototype" -], function(jQuery, alert, prompt){ +], function(jQuery, alert, prompt, registry){ function toggleApplyVisibility(select) { if ($(select).value == 1) { @@ -40,16 +40,21 @@ function getFrontTab() { function checkOptionsPanelVisibility(){ if($('manage-options-panel')){ - var panel = $('manage-options-panel').up('.fieldset'), + var panelId = 'manage-options-panel', + panel = $(panelId), + panelFieldSet = panel.up('.fieldset'), activePanelClass = 'selected-type-options'; if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){ - panel.show(); - panel.addClass(activePanelClass); + panelFieldSet.show(); + jQuery(panel).addClass(activePanelClass); + registry.get(panelId, function () { + jQuery('#' + panelId).trigger('render'); + }); } else { - panel.hide(); - panel.removeClass(activePanelClass); + panelFieldSet.hide(); + jQuery(panel).removeClass(activePanelClass); } } } @@ -197,22 +202,22 @@ function switchDefaultValueField() setRowVisibility('frontend_class', false); break; - <?php foreach ($this->helper('Magento\Catalog\Helper\Data')->getAttributeHiddenFields() as $type => $fields): ?> - case '<?= /* @escapeNotVerified */ $type ?>': + <?php foreach ($this->helper(Magento\Catalog\Helper\Data::class)->getAttributeHiddenFields() as $type => $fields) :?> + case '<?= $block->escapeJs($type) ?>': var isFrontTabHidden = false; - <?php foreach ($fields as $one): ?> - <?php if ($one == '_front_fieldset'): ?> + <?php foreach ($fields as $one) :?> + <?php if ($one == '_front_fieldset') :?> getFrontTab().hide(); isFrontTabHidden = true; - <?php elseif ($one == '_default_value'): ?> + <?php elseif ($one == '_default_value') :?> defaultValueTextVisibility = defaultValueTextareaVisibility = defaultValueDateVisibility = defaultValueYesnoVisibility = false; - <?php elseif ($one == '_scope'): ?> + <?php elseif ($one == '_scope') :?> scopeVisibility = false; - <?php else: ?> - setRowVisibility('<?= /* @escapeNotVerified */ $one ?>', false); + <?php else :?> + setRowVisibility('<?= $block->escapeJs($one) ?>', false); <?php endif; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml index f3d39257c266c..1d5d251f00de9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/labels.phtml @@ -4,15 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Eav\Block\Adminhtml\Attribute\Edit\Options\Labels */ ?> <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="manage-titles-wrapper"> <div class="fieldset-wrapper-title"> <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#manage-titles-content"> - <span><?= /* @escapeNotVerified */ __('Manage Titles (Size, Color, etc.)') ?></span> + <span><?= $block->escapeHtml(__('Manage Titles (Size, Color, etc.)')) ?></span> </strong> </div> <div class="fieldset-wrapper-content in collapse" id="manage-titles-content"> @@ -21,17 +19,23 @@ <table class="admin__control-table" id="attribute-labels-table"> <thead> <tr> - <?php foreach ($block->getStores() as $_store): ?> - <th class="col-store-view"><?= /* @escapeNotVerified */ $_store->getName() ?></th> + <?php foreach ($block->getStores() as $_store) :?> + <th class="col-store-view"><?= $block->escapeHtml($_store->getName()) ?></th> <?php endforeach; ?> </tr> </thead> <tbody> <tr> <?php $_labels = $block->getLabelValues() ?> - <?php foreach ($block->getStores() as $_store): ?> + <?php foreach ($block->getStores() as $_store) :?> <td class="col-store-view"> - <input class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option<?php endif; ?>" type="text" name="frontend_label[<?= /* @escapeNotVerified */ $_store->getId() ?>]" value="<?= $block->escapeHtml($_labels[$_store->getId()]) ?>"<?php if ($block->getReadOnly()):?> disabled="disabled"<?php endif;?>/> + <input class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID) :?> required-option<?php endif; ?>" + type="text" + name="frontend_label[<?= $block->escapeHtmlAttr($_store->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($_labels[$_store->getId()]) ?>" + <?php if ($block->getReadOnly()) :?> + disabled="disabled" + <?php endif;?>/> </td> <?php endforeach; ?> </tr> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml index f812a27f87ad9..e5f8a360c334c 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Eav\Block\Adminhtml\Attribute\Edit\Options\Options */ $stores = $block->getStoresSortedBySortOrder(); @@ -23,8 +21,8 @@ $stores = $block->getStoresSortedBySortOrder(); <span><?= $block->escapeHtml(__('Is Default')) ?></span> </th> <?php - foreach ($stores as $_store): ?> - <th<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> class="_required"<?php endif; ?>> + foreach ($stores as $_store) :?> + <th<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID) :?> class="_required"<?php endif; ?>> <span><?= $block->escapeHtml(__($_store->getName())) ?></span> </th> <?php endforeach; @@ -43,7 +41,7 @@ $stores = $block->getStoresSortedBySortOrder(); </tr> <tr> <th colspan="<?= (int) $storetotal ?>" class="col-actions-add"> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?> + <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) :?> <button id="add_new_option_button" data-action="add_new_row" title="<?= $block->escapeHtml(__('Add Option')) ?>" type="button" class="action- scalable add"> @@ -59,22 +57,22 @@ $stores = $block->getStoresSortedBySortOrder(); <script id="row-template" type="text/x-magento-template"> <tr <% if (data.rowClasses) { %>class="<%- data.rowClasses %>"<% } %>> <td class="col-draggable"> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()): ?> + <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) :?> <div data-role="draggable-handle" class="draggable-handle" title="<?= $block->escapeHtml(__('Sort Option')) ?>"> </div> <?php endif; ?> - <input data-role="order" type="hidden" name="option[order][<%- data.id %>]" value="<%- data.sort_order %>" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()): ?> disabled="disabled"<?php endif; ?>/> + <input data-role="order" type="hidden" name="option[order][<%- data.id %>]" value="<%- data.sort_order %>" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()) :?> disabled="disabled"<?php endif; ?>/> </td> <td class="col-default control-table-actions-cell"> - <input class="input-radio" type="<%- data.intype %>" name="default[]" value="<%- data.id %>" <%- data.checked %><?php if ($block->getReadOnly()):?>disabled="disabled"<?php endif;?>/> + <input class="input-radio" type="<%- data.intype %>" name="default[]" value="<%- data.id %>" <%- data.checked %><?php if ($block->getReadOnly()) :?>disabled="disabled"<?php endif;?>/> </td> - <?php foreach ($stores as $_store): ?> - <td class="col-<%- data.id %>"><input name="option[value][<%- data.id %>][<?= (int) $_store->getId() ?>]" value="<%- data.store<?= /* @noEscape */ (int) $_store->getId() ?> %>" class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option required-unique<?php endif; ?>" type="text" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()):?> disabled="disabled"<?php endif;?>/></td> + <?php foreach ($stores as $_store) :?> + <td class="col-<%- data.id %>"><input name="option[value][<%- data.id %>][<?= (int) $_store->getId() ?>]" value="<%- data.store<?= /* @noEscape */ (int) $_store->getId() ?> %>" class="input-text<?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID) :?> required-option required-unique<?php endif; ?>" type="text" <?php if ($block->getReadOnly() || $block->canManageOptionDefaultOnly()) :?> disabled="disabled"<?php endif;?>/></td> <?php endforeach; ?> <td id="delete_button_container_<%- data.id %>" class="col-delete"> <input type="hidden" class="delete-flag" name="option[delete][<%- data.id %>]" value="" /> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?> + <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) :?> <button id="delete_button_<%- data.id %>" title="<?= $block->escapeHtml(__('Delete')) ?>" type="button" class="action- scalable delete delete-option" > @@ -86,9 +84,9 @@ $stores = $block->getStoresSortedBySortOrder(); </script> <?php $values = []; - foreach($block->getOptionValues() as $value) { + foreach ($block->getOptionValues() as $value) { $value = $value->getData(); - $values[] = is_array($value) ? array_map(function($str) { + $values[] = is_array($value) ? array_map(function ($str) { return htmlspecialchars_decode($str, ENT_QUOTES); }, $value) : $value; } diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml index 9621b9a57168c..dd1009cc5e033 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml @@ -4,8 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main */ ?> <div class="attribute-set"> @@ -31,11 +30,11 @@ </div> <div class="attribute-set-col fieldset-wrapper"> <div class="fieldset-wrapper-title"> - <span class="title"><?= /* @escapeNotVerified */ __('Groups') ?></span> + <span class="title"><?= $block->escapeHtml(__('Groups')) ?></span> </div> - <?php if (!$block->getIsReadOnly()): ?> - <?= /* @escapeNotVerified */ $block->getAddGroupButton() ?> <?= /* @escapeNotVerified */ $block->getDeleteGroupButton() ?> - <p class="note-block"><?= /* @escapeNotVerified */ __('Double click on a group to rename it.') ?></p> + <?php if (!$block->getIsReadOnly()) :?> + <?= /* @noEscape */ $block->getAddGroupButton() ?> <?= /* @noEscape */ $block->getDeleteGroupButton() ?> + <p class="note-block"><?= $block->escapeHtml(__('Double click on a group to rename it.')) ?></p> <?php endif; ?> <?= $block->getSetsFilterHtml() ?> @@ -43,7 +42,7 @@ </div> <div class="attribute-set-col fieldset-wrapper"> <div class="fieldset-wrapper-title"> - <span class="title"><?= /* @escapeNotVerified */ __('Unassigned Attributes') ?></span> + <span class="title"><?= $block->escapeHtml(__('Unassigned Attributes')) ?></span> </div> <div id="tree-div2" class="attribute-set-tree"></div> <script id="ie-deferred-loader" defer="defer" src="//:"></script> @@ -58,8 +57,8 @@ ], function(jQuery, prompt, alert){ //<![CDATA[ - var allowDragAndDrop = <?= /* @escapeNotVerified */ ($block->getIsReadOnly() ? 'false' : 'true') ?>; - var canEditGroups = <?= /* @escapeNotVerified */ ($block->getIsReadOnly() ? 'false' : 'true') ?>; + var allowDragAndDrop = <?= ($block->getIsReadOnly() ? 'false' : 'true') ?>; + var canEditGroups = <?= ($block->getIsReadOnly() ? 'false' : 'true') ?>; var TreePanels = function() { // shorthand @@ -86,7 +85,7 @@ }); tree.setRootNode(this.root); - buildCategoryTree(this.root, <?= /* @escapeNotVerified */ $block->getGroupTreeJson() ?>); + buildCategoryTree(this.root, <?= /* @noEscape */ $block->getGroupTreeJson() ?>); // render the tree tree.render(); this.root.expand(false, false); @@ -94,7 +93,7 @@ this.ge = new Ext.tree.TreeEditor(tree, { allowBlank:false, - blankText:'<?= /* @escapeNotVerified */ __('A name is required.') ?>', + blankText:'<?= $block->escapeJs(__('A name is required.')) ?>', selectOnFocus:true, cls:'folder' }); @@ -125,7 +124,7 @@ id:'free' }); tree2.setRootNode(this.root2); - buildCategoryTree(this.root2, <?= /* @escapeNotVerified */ $block->getAttributeTreeJson() ?>); + buildCategoryTree(this.root2, <?= /* @noEscape */ $block->getAttributeTreeJson() ?>); this.root2.addListener('beforeinsert', editSet.rightBeforeInsert); this.root2.addListener('beforeappend', editSet.rightBeforeAppend); @@ -196,14 +195,14 @@ } } } - } - node.appendChild(newNode); - newNode.addListener('click', editSet.unregister); } + node.appendChild(newNode); + newNode.addListener('click', editSet.unregister); } } } } + } editSet = function () { @@ -280,8 +279,8 @@ addGroup : function() { prompt({ - title: "<?= /* @escapeNotVerified */ __('Add New Group') ?>", - content: "<?= /* @escapeNotVerified */ __('Please enter a new group name.') ?>", + title: "<?= $block->escapeJs($block->escapeHtml(__('Add New Group'))) ?>", + content: "<?= $block->escapeJs($block->escapeHtml(__('Please enter a new group name.'))) ?>", value: "", validation: true, validationRules: ['required-entry'], @@ -346,7 +345,7 @@ } for (var i=0; i < TreePanels.root.childNodes.length; i++) { if (TreePanels.root.childNodes[i].text.toLowerCase() == name.toLowerCase() && TreePanels.root.childNodes[i].id != exceptNodeId) { - errorText = '<?= /* @escapeNotVerified */ __('An attribute group named "/name/" already exists.') ?>'; + errorText = '<?= $block->escapeJs(__('An attribute group named "/name/" already exists.')) ?>'; alert({ content: errorText.replace("/name/",name) }); @@ -374,7 +373,7 @@ editSet.req.form_key = FORM_KEY; } var req = {data : Ext.util.JSON.encode(editSet.req)}; - var con = new Ext.lib.Ajax.request('POST', '<?= /* @escapeNotVerified */ $block->getMoveUrl() ?>', {success:editSet.success,failure:editSet.failure}, req); + var con = new Ext.lib.Ajax.request('POST', '<?= $block->escapeJs($block->escapeUrl($block->getMoveUrl())) ?>', {success:editSet.success,failure:editSet.failure}, req); }, success : function(o) { @@ -449,7 +448,7 @@ rightRemove : function(tree, nodeThis, node) { if( nodeThis.firstChild == null && node.id != 'empty' ) { var newNode = new Ext.tree.TreeNode({ - text : '<?= /* @escapeNotVerified */ __('Empty') ?>', + text : '<?= $block->escapeJs(__('Empty')) ?>', id : 'empty', cls : 'folder', is_user_defined : 1, diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml index c1af14389fe59..227ed4be81fae 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/add.phtml @@ -8,7 +8,7 @@ <script> require(['jquery', "mage/mage"], function(jQuery){ - jQuery('#<?= /* @escapeNotVerified */ $block->getFormId() ?>').mage('form').mage('validation'); + jQuery('#<?= $block->escapeJs($block->getFormId()) ?>').mage('form').mage('validation'); }); </script> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml index 902c6932f0ae1..c0928f4723b50 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/toolbar/main.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml('grid') ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/configure.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/configure.phtml index 75027d5e043fb..32466a1dfa965 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/configure.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/configure.phtml @@ -3,10 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - - ?> +?> <div id="product_composite_configure" class="product-configure-popup" style="display:none;"> <iframe name="product_composite_configure_iframe" id="product_composite_configure_iframe" style="width:0; height:0; border:0px solid #fff; position:absolute; top:-1000px; left:-1000px" onload="window.productConfigure && productConfigure.onLoadIFrame()"></iframe> <form action="" method="post" id="product_composite_configure_form" enctype="multipart/form-data" onsubmit="productConfigure.onConfirmBtn(); return false;" target="product_composite_configure_iframe"> @@ -19,7 +16,7 @@ <div id="product_composite_configure_form_confirmed" style="display:none;"></div> </div> <input type="hidden" name="as_js_varname" value="iFrameResponse" /> - <input type="hidden" name="form_key" value="<?= /* @escapeNotVerified */ $block->getFormKey() ?>" /> + <input type="hidden" name="form_key" value="<?= $block->escapeHtmlAttr($block->getFormKey()) ?>" /> </form> <div id="product_composite_configure_confirmed" style="display:none;"></div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options.phtml index acc80fa6ea6b0..6a83ece330441 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options.phtml @@ -3,24 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Adminhtml\Product\Composite\Fieldset\Options */ ?> <?php $options = $block->decorateArray($block->getOptions()); ?> -<?php if (count($options)): ?> +<?php if (count($options)) :?> -<?= $block->getChildHtml('options_js') ?> + <?= $block->getChildHtml('options_js') ?> -<fieldset id="product_composite_configure_fields_options" class="fieldset admin__fieldset <?= $block->getIsLastFieldset() ? 'last-fieldset' : '' ?>"> - <legend class="legend admin__legend"> - <span><?= /* @escapeNotVerified */ __('Custom Options') ?></span> - </legend><br> - <?php foreach ($options as $option): ?> - <?= $block->getOptionHtml($option) ?> - <?php endforeach;?> -</fieldset> + <fieldset id="product_composite_configure_fields_options" + class="fieldset admin__fieldset <?= $block->getIsLastFieldset() ? 'last-fieldset' : '' ?>"> + <legend class="legend admin__legend"> + <span><?= $block->escapeHtml(__('Custom Options')) ?></span> + </legend><br> + <?php foreach ($options as $option) :?> + <?= $block->getOptionHtml($option) ?> + <?php endforeach;?> + </fieldset> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml index 30c05c2ec689b..8adffb752187b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/date.phtml @@ -3,82 +3,82 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Date */ ?> <?php $_option = $block->getOption(); ?> -<?php $_optionId = $_option->getId(); ?> -<div class="admin__field field<?php if ($_option->getIsRequire()) echo ' required _required' ?>"> - <label class="label admin__field-label"> - <?= $block->escapeHtml($_option->getTitle()) ?> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> - </label> - <div class="admin__field-control control"> +<?php $_optionId = (int)$_option->getId(); ?> +<div class="admin__field field<?= $_option->getIsRequire() ? ' required _required' : '' ?>"> + <label class="label admin__field-label"> + <?= $block->escapeHtml($_option->getTitle()) ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> + </label> + <div class="admin__field-control control"> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME - || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE): ?> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE) :?> - <?= $block->getDateHtml() ?> + <?= $block->getDateHtml() ?> - <?php if (!$block->useCalendar()): ?> - <script> -require([ - "prototype", - "Magento_Catalog/catalog/product/composite/configure" -], function(){ + <?php if (!$block->useCalendar()) :?> + <script> + require([ + "prototype", + "Magento_Catalog/catalog/product/composite/configure" + ], function(){ - window.dateOption = productConfigure.opConfig.dateOption; - Event.observe('options_<?= /* @escapeNotVerified */ $_optionId ?>_month', 'change', dateOption.reloadMonth.bind(dateOption)); - Event.observe('options_<?= /* @escapeNotVerified */ $_optionId ?>_year', 'change', dateOption.reloadMonth.bind(dateOption)); -}); -</script> - <?php endif; ?> + window.dateOption = productConfigure.opConfig.dateOption; + Event.observe('options_<?= /* @noEscape */ $_optionId ?>_month', 'change', dateOption.reloadMonth.bind(dateOption)); + Event.observe('options_<?= /* @noEscape */ $_optionId ?>_year', 'change', dateOption.reloadMonth.bind(dateOption)); + }); + </script> + <?php endif; ?> - <?php endif; ?> + <?php endif; ?> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME - || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME): ?> - <span class="time-picker"><?= $block->getTimeHtml() ?></span> - <?php endif; ?> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME) :?> + <span class="time-picker"><?= $block->getTimeHtml() ?></span> + <?php endif; ?> - <input type="hidden" name="validate_datetime_<?= /* @escapeNotVerified */ $_optionId ?>" class="validate-datetime-<?= /* @escapeNotVerified */ $_optionId ?>" value="" /> - <script> -require([ - "jquery", - "mage/backend/validation" -], function(jQuery){ + <input type="hidden" + name="validate_datetime_<?= /* @noEscape */ $_optionId ?>" + class="validate-datetime-<?= /* @noEscape */ $_optionId ?>" + value="" /> + <script> + require([ + "jquery", + "mage/backend/validation" + ], function(jQuery){ - //<![CDATA[ -<?php if ($_option->getIsRequire()): ?> - jQuery.validator.addMethod('validate-datetime-<?= /* @escapeNotVerified */ $_optionId ?>', function(v) { - var dateTimeParts = jQuery('.datetime-picker[id^="options_<?= /* @escapeNotVerified */ $_optionId ?>"]'); - for (var i=0; i < dateTimeParts.length; i++) { - if (dateTimeParts[i].value == "") return false; - } - return true; - }, '<?= $block->escapeJs(__('This is a required option.')) ?>'); -<?php else: ?> - jQuery.validator.addMethod('validate-datetime-<?= /* @escapeNotVerified */ $_optionId ?>', function(v) { - var dateTimeParts = jQuery('.datetime-picker[id^="options_<?= /* @escapeNotVerified */ $_optionId ?>"]'); - var hasWithValue = false, hasWithNoValue = false; - var pattern = /day_part$/i; - for (var i=0; i < dateTimeParts.length; i++) { - if (! pattern.test(dateTimeParts[i].id)) { - if (dateTimeParts[i].value === "") { - hasWithValue = true; - } else { - hasWithNoValue = true; + //<![CDATA[ + <?php if ($_option->getIsRequire()) :?> + jQuery.validator.addMethod('validate-datetime-<?= /* @noEscape */ $_optionId ?>', function(v) { + var dateTimeParts = jQuery('.datetime-picker[id^="options_<?= /* @noEscape */ $_optionId ?>"]'); + for (var i=0; i < dateTimeParts.length; i++) { + if (dateTimeParts[i].value == "") return false; } - } - } - return hasWithValue ^ hasWithNoValue; - }, '<?= $block->escapeJs(__('The field isn\'t complete.')) ?>'); -<?php endif; ?> - //]]> - -}); -</script> - </div> + return true; + }, '<?= $block->escapeJs(__('This is a required option.')) ?>'); + <?php else :?> + jQuery.validator.addMethod('validate-datetime-<?= /* @noEscape */ $_optionId ?>', function(v) { + var dateTimeParts = jQuery('.datetime-picker[id^="options_<?= /* @noEscape */ $_optionId ?>"]'); + var hasWithValue = false, hasWithNoValue = false; + var pattern = /day_part$/i; + for (var i=0; i < dateTimeParts.length; i++) { + if (! pattern.test(dateTimeParts[i].id)) { + if (dateTimeParts[i].value === "") { + hasWithValue = true; + } else { + hasWithNoValue = true; + } + } + } + return hasWithValue ^ hasWithNoValue; + }, '<?= $block->escapeJs(__('The field isn\'t complete.')) ?>'); + <?php endif; ?> + //]]> + + }); + </script> + </div> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml index 4ad7a95c91980..da0b3b36d561e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/file.phtml @@ -3,15 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\File */ ?> <?php $_option = $block->getOption(); ?> <?php $_fileInfo = $block->getFileInfo(); ?> <?php $_fileExists = $_fileInfo->hasData() ? true : false; ?> -<?php $_fileName = 'options_' . $_option->getId() . '_file'; ?> +<?php $_fileName = 'options_' . (int)$_option->getId() . '_file'; ?> <?php $_fieldNameAction = $_fileName . '_action'; ?> <?php $_fieldValueAction = $_fileExists ? 'save_old' : 'save_new'; ?> <?php $_fileNamed = $_fileName . '_name'; ?> @@ -21,11 +18,11 @@ require(['prototype'], function(){ //<![CDATA[ - opFile<?= /* @escapeNotVerified */ $_rand ?> = { + opFile<?= /* @noEscape */ $_rand ?> = { initializeFile: function(inputBox) { - this.inputFile = inputBox.select('input[name="<?= /* @escapeNotVerified */ $_fileName ?>"]')[0]; - this.inputFileAction = inputBox.select('input[name="<?= /* @escapeNotVerified */ $_fieldNameAction ?>"]')[0]; - this.fileNameBox = inputBox.up('dd').select('.<?= /* @escapeNotVerified */ $_fileNamed ?>')[0]; + this.inputFile = inputBox.select('input[name="<?= /* @noEscape */ $_fileName ?>"]')[0]; + this.inputFileAction = inputBox.select('input[name="<?= /* @noEscape */ $_fieldNameAction ?>"]')[0]; + this.fileNameBox = inputBox.up('dd').select('.<?= /* @noEscape */ $_fileNamed ?>')[0]; }, toggleFileChange: function(inputBox) { @@ -62,42 +59,42 @@ require(['prototype'], function(){ }); </script> -<div class="admin__field <?php if ($_option->getIsRequire()) echo ' required _required' ?>"> +<div class="admin__field <?= $_option->getIsRequire() ? ' required _required' : '' ?>"> <label class="admin__field-label label"> <?= $block->escapeHtml($_option->getTitle()) ?> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> </label> <div class="admin__field-control control"> - <?php if ($_fileExists): ?> + <?php if ($_fileExists) :?> <span class="<?= /* @noEscape */ $_fileNamed ?>"><?= $block->escapeHtml($_fileInfo->getTitle()) ?></span> - <a href="javascript:void(0)" class="label" onclick="opFile<?= /* @escapeNotVerified */ $_rand ?>.toggleFileChange($(this).next('.input-box'))"> - <?= /* @escapeNotVerified */ __('Change') ?> + <a href="javascript:void(0)" class="label" onclick="opFile<?= /* @noEscape */ $_rand ?>.toggleFileChange($(this).next('.input-box'))"> + <?= $block->escapeHtml(__('Change')) ?> </a>  - <?php if (!$_option->getIsRequire()): ?> - <input type="checkbox" onclick="opFile<?= /* @escapeNotVerified */ $_rand ?>.toggleFileDelete($(this), $(this).next('.input-box'))" price="<?= /* @escapeNotVerified */ $block->getCurrencyPrice($_option->getPrice(true)) ?>"/> - <span class="label"><?= /* @escapeNotVerified */ __('Delete') ?></span> + <?php if (!$_option->getIsRequire()) :?> + <input type="checkbox" onclick="opFile<?= /* @noEscape */ $_rand ?>.toggleFileDelete($(this), $(this).next('.input-box'))" price="<?= $block->escapeHtmlAttr($block->getCurrencyPrice($_option->getPrice(true))) ?>"/> + <span class="label"><?= $block->escapeHtml(__('Delete')) ?></span> <?php endif; ?> <?php endif; ?> <div class="input-box" <?= $_fileExists ? 'style="display:none"' : '' ?>> <!-- ToDo UI: add appropriate file class when z-index issue in ui dialog will be resolved --> - <input type="file" name="<?= /* @noEscape */ $_fileName ?>" class="product-custom-option<?= $_option->getIsRequire() ? ' required-entry' : '' ?>" price="<?= /* @escapeNotVerified */ $block->getCurrencyPrice($_option->getPrice(true)) ?>" <?= $_fileExists ? 'disabled="disabled"' : '' ?>/> - <input type="hidden" name="<?= /* @escapeNotVerified */ $_fieldNameAction ?>" value="<?= /* @escapeNotVerified */ $_fieldValueAction ?>" /> + <input type="file" name="<?= /* @noEscape */ $_fileName ?>" class="product-custom-option<?= $_option->getIsRequire() ? ' required-entry' : '' ?>" price="<?= $block->escapeHtmlAttr($block->getCurrencyPrice($_option->getPrice(true))) ?>" <?= $_fileExists ? 'disabled="disabled"' : '' ?>/> + <input type="hidden" name="<?= /* @noEscape */ $_fieldNameAction ?>" value="<?= /* @noEscape */ $_fieldValueAction ?>" /> - <?php if ($_option->getFileExtension()): ?> + <?php if ($_option->getFileExtension()) :?> <div class="admin__field-note"> - <span><?= /* @escapeNotVerified */ __('Compatible file extensions to upload') ?>: <strong><?= /* @escapeNotVerified */ $_option->getFileExtension() ?></strong></span> + <span><?= $block->escapeHtml(__('Compatible file extensions to upload')) ?>: <strong><?= $block->escapeHtml($_option->getFileExtension()) ?></strong></span> </div> <?php endif; ?> - <?php if ($_option->getImageSizeX() > 0): ?> + <?php if ($_option->getImageSizeX() > 0) :?> <div class="admin__field-note"> - <span><?= /* @escapeNotVerified */ __('Maximum image width') ?>: <strong><?= /* @escapeNotVerified */ $_option->getImageSizeX() ?> <?= /* @escapeNotVerified */ __('px.') ?></strong></span> + <span><?= $block->escapeHtml(__('Maximum image width')) ?>: <strong><?= (int)$_option->getImageSizeX() ?> <?= $block->escapeHtml(__('px.')) ?></strong></span> </div> <?php endif; ?> - <?php if ($_option->getImageSizeY() > 0): ?> + <?php if ($_option->getImageSizeY() > 0) :?> <div class="admin__field-note"> - <span><?= /* @escapeNotVerified */ __('Maximum image height') ?>: <strong><?= /* @escapeNotVerified */ $_option->getImageSizeY() ?> <?= /* @escapeNotVerified */ __('px.') ?></strong></span> + <span><?= $block->escapeHtml(__('Maximum image height')) ?>: <strong><?= (int)$_option->getImageSizeY() ?> <?= $block->escapeHtml(__('px.')) ?></strong></span> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/select.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/select.phtml index af09bbe0acd9d..2218ce5d29671 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/select.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/select.phtml @@ -3,21 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Select */ ?> <?php $_option = $block->getOption(); ?> -<div class="admin__field field<?php if ($_option->getIsRequire()) echo ' required _required' ?>"> +<div class="admin__field field<?= $_option->getIsRequire() ? ' required _required' : '' ?>"> <label class="label admin__field-label"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> </label> <div class="control admin__field-control"> <?= $block->getValuesHtml() ?> - <?php if ($_option->getIsRequire()): ?> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX): ?> - <span id="options-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></span> + <?php if ($_option->getIsRequire()) :?> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX) :?> + <span id="options-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></span> <?php endif; ?> <?php endif;?> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/text.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/text.phtml index 11fba22ea8139..d1a019911d581 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/text.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/options/type/text.phtml @@ -3,26 +3,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Text */ ?> <?php $_option = $block->getOption(); ?> -<div class="field admin__field<?php if ($_option->getIsRequire()) echo ' required _required' ?>"> +<div class="field admin__field<?= $_option->getIsRequire() ? ' required _required' : '' ?>"> <label class="admin__field-label label"> <?= $block->escapeHtml($_option->getTitle()) ?> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> </label> <div class="control admin__field-control"> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD): ?> - <input type="text" id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text" class="input-text admin__control-text <?= $_option->getIsRequire() ? ' required-entry' : '' ?> <?= /* @escapeNotVerified */ $_option->getMaxCharacters() ? ' validate-length maximum-length-' . $_option->getMaxCharacters() : '' ?> product-custom-option" name="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" value="<?= $block->escapeHtml($block->getDefaultValue()) ?>" price="<?= /* @escapeNotVerified */ $block->getCurrencyPrice($_option->getPrice(true)) ?>" /> - <?php elseif ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA): ?> - <textarea id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text" class="admin__control-textarea <?= $_option->getIsRequire() ? ' required-entry' : '' ?> <?= /* @escapeNotVerified */ $_option->getMaxCharacters() ? ' validate-length maximum-length-' . $_option->getMaxCharacters() : '' ?> product-custom-option" name="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" rows="5" cols="25" price="<?= /* @escapeNotVerified */ $block->getCurrencyPrice($_option->getPrice(true)) ?>"><?= $block->escapeHtml($block->getDefaultValue()) ?></textarea> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD) :?> + <input type="text" + id="options_<?= $block->escapeHtmlAttr($_option->getId()) ?>_text" + class="input-text admin__control-text <?= $_option->getIsRequire() ? ' required-entry' : '' ?> <?= $_option->getMaxCharacters() ? ' validate-length maximum-length-' . (int) $_option->getMaxCharacters() : '' ?> product-custom-option" + name="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + value="<?= $block->escapeHtmlAttr($block->getDefaultValue()) ?>" + price="<?= $block->escapeHtmlAttr($block->getCurrencyPrice($_option->getPrice(true))) ?>" /> + <?php elseif ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA) :?> + <textarea id="options_<?= $block->escapeHtmlAttr($_option->getId()) ?>_text" + class="admin__control-textarea <?= $_option->getIsRequire() ? ' required-entry' : '' ?> <?= $_option->getMaxCharacters() ? ' validate-length maximum-length-' . (int) $_option->getMaxCharacters() : '' ?> product-custom-option" + name="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + rows="5" + cols="25" + price="<?= $block->escapeHtmlAttr($block->getCurrencyPrice($_option->getPrice(true))) ?>"><?= $block->escapeHtml($block->getDefaultValue()) ?></textarea> <?php endif;?> - <?php if ($_option->getMaxCharacters()): ?> - <p class="note"><?= /* @escapeNotVerified */ __('Maximum number of characters:') ?> <strong><?= /* @escapeNotVerified */ $_option->getMaxCharacters() ?></strong></p> + <?php if ($_option->getMaxCharacters()) :?> + <p class="note"><?= $block->escapeHtml(__('Maximum number of characters:')) ?> <strong><?= (int) $_option->getMaxCharacters() ?></strong></p> <?php endif; ?> </div> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/qty.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/qty.phtml index 487c9b8e8f2b7..4726bdc0930fd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/qty.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/composite/fieldset/qty.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Adminhtml\Product\Composite\Fieldset\Qty */ ?> @@ -13,9 +10,9 @@ <fieldset id="product_composite_configure_fields_qty" class="fieldset product-composite-qty-block admin__fieldset <?= $block->getIsLastFieldset() ? 'last-fieldset' : '' ?>"> <div class="field admin__field"> - <label class="label admin__field-label"><span><?= /* @escapeNotVerified */ __('Quantity') ?></span></label> + <label class="label admin__field-label"><span><?= $block->escapeHtml(__('Quantity')) ?></span></label> <div class="control admin__field-control"> - <input id="product_composite_configure_input_qty" class="input-text admin__control-text qty" type="text" name="qty" value="<?= /* @escapeNotVerified */ $block->getQtyValue() * 1 ?>"> + <input id="product_composite_configure_input_qty" class="input-text admin__control-text qty" type="text" name="qty" value="<?= $block->getQtyValue() * 1 ?>"> </div> </div> </fieldset> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml index 7c25c3686eadc..66df098a194ae 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml @@ -3,11 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + /** * @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit */ @@ -17,11 +16,11 @@ <div id="product-template-suggest-container" class="suggest-expandable"> <div class="action-dropdown"> <button type="button" class="action-toggle" data-mage-init='{"dropdown":{}}' data-toggle="dropdown"> - <span><?= /* @escapeNotVerified */ $block->getAttributeSetName() ?></span> + <span><?= $block->escapeHtml($block->getAttributeSetName()) ?></span> </button> <ul class="dropdown-menu"> <li><input type="text" id="product-template-suggest" class="search" - placeholder="<?= /* @noEscape */ __('start typing to search template') ?>"/></li> + placeholder="<?= $block->escapeHtmlAttr(__('start typing to search template')) ?>"/></li> </ul> </div> </div> @@ -30,32 +29,32 @@ <input type="checkbox" id="product-online-switcher" name="product-online-switcher" /> <label class="switcher-label" for="product-online-switcher" - data-text-on="<?= /* @escapeNotVerified */ __('Product online') ?>" - data-text-off="<?= /* @escapeNotVerified */ __('Product offline') ?>" - title="<?= /* @escapeNotVerified */ __('Product online status') ?>"></label> + data-text-on="<?= $block->escapeHtmlAttr(__('Product online')) ?>" + data-text-off="<?= $block->escapeHtmlAttr(__('Product offline')) ?>" + title="<?= $block->escapeHtmlAttr(__('Product online status')) ?>"></label> </div> - <?php if ($block->getProductId()): ?> + <?php if ($block->getProductId()) :?> <?= $block->getDeleteButtonHtml() ?> <?php endif; ?> - <?php if ($block->getProductSetId()): ?> + <?php if ($block->getProductSetId()) :?> <?= $block->getChangeAttributeSetButtonHtml() ?> <?= $block->getSaveSplitButtonHtml() ?> <?php endif; ?> <?= $block->getBackButtonHtml() ?> </div> </div> -<?php if ($block->getUseContainer()): ?> -<form action="<?= /* @escapeNotVerified */ $block->getSaveUrl() ?>" method="post" enctype="multipart/form-data" - data-form="edit-product" data-product-id="<?= /* @escapeNotVerified */ $block->getProduct()->getId() ?>"> +<?php if ($block->getUseContainer()) :?> +<form action="<?= $block->escapeUrl($block->getSaveUrl()) ?>" method="post" enctype="multipart/form-data" + data-form="edit-product" data-product-id="<?= $block->escapeHtmlAttr($block->getProduct()->getId()) ?>"> <?php endif; ?> <?= $block->getBlockHtml('formkey') ?> <div data-role="tabs" id="product-edit-form-tabs"></div> <?php /* @TODO: remove id after elimination of setDestElementId('product-edit-form-tabs') */?> <?= $block->getChildHtml('product-type-tabs') ?> - <input type="hidden" id="product_type_id" value="<?= /* @escapeNotVerified */ $block->getProduct()->getTypeId() ?>"/> - <input type="hidden" id="attribute_set_id" value="<?= /* @escapeNotVerified */ $block->getProduct()->getAttributeSetId() ?>"/> + <input type="hidden" id="product_type_id" value="<?= $block->escapeHtmlAttr($block->getProduct()->getTypeId()) ?>"/> + <input type="hidden" id="attribute_set_id" value="<?= $block->escapeHtmlAttr($block->getProduct()->getAttributeSetId()) ?>"/> <button type="submit" class="hidden"></button> -<?php if ($block->getUseContainer()): ?> +<?php if ($block->getUseContainer()) :?> </form> <?php endif; ?> <script> @@ -130,10 +129,10 @@ require([ } } }); - $form.mage('validation', {validationUrl: '<?= /* @escapeNotVerified */ $block->getValidationUrl() ?>'}); + $form.mage('validation', {validationUrl: '<?= $block->escapeJs($block->escapeUrl($block->getValidationUrl())) ?>'}); - var masks = <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getFieldsAutogenerationMasks()) ?>; - var availablePlaceholders = <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getAttributesAllowedForAutogeneration()) ?>; + var masks = <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getFieldsAutogenerationMasks()) ?>; + var availablePlaceholders = <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getAttributesAllowedForAutogeneration()) ?>; var Autogenerator = function(masks) { this._masks = masks || {}; this._fieldReverseIndex = this._buildReverseIndex(this._masks); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml index a3b0b32e4c29a..056cf014f769a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml @@ -4,18 +4,21 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute */ ?> -<form action="<?= /* @escapeNotVerified */ $block->getSaveUrl() ?>" method="post" id="attributes-edit-form" class="attributes-edit-form" enctype="multipart/form-data"> +<form action="<?= $block->escapeUrl($block->getSaveUrl()) ?>" + method="post" + id="attributes-edit-form" + class="attributes-edit-form" + enctype="multipart/form-data"> <?= $block->getBlockHtml('formkey') ?> </form> <script type="text/x-magento-init"> { "#attributes-edit-form": { "Magento_Catalog/catalog/product/edit/attribute": { - "validationUrl": "<?= /* @escapeNotVerified */ $block->getValidationUrl() ?>" + "validationUrl": "<?= $block->escapeJs($block->escapeUrl($block->getValidationUrl())) ?>" } } } diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml index 64c8ba7dcf49f..792af12494af6 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** @var Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Inventory $block */ ?> <script> @@ -40,326 +39,363 @@ if (!is_numeric($defaultMinSaleQty)) { <div class="fieldset-wrapper form-inline advanced-inventory-edit"> <div class="fieldset-wrapper-title"> <strong class="title"> - <span><?= /* @escapeNotVerified */ __('Advanced Inventory') ?></span> + <span><?= $block->escapeHtml(__('Advanced Inventory')) ?></span> </strong> </div> <div class="fieldset-wrapper-content"> <fieldset class="fieldset" id="table_cataloginventory"> <div class="field"> <label class="label" for="inventory_manage_stock"> - <span><?= /* @escapeNotVerified */ __('Manage Stock') ?></span> + <span><?= $block->escapeHtml(__('Manage Stock')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> - <select id="inventory_manage_stock" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[manage_stock]" + <select id="inventory_manage_stock" name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[manage_stock]" class="select" disabled="disabled"> - <option value="1"><?= /* @escapeNotVerified */ __('Yes') ?></option> - <option - value="0"<?php if ($block->getFieldValue('manage_stock') == 0): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('No') ?></option> + <option value="1"><?= $block->escapeHtml(__('Yes')) ?></option> + <option value="0" + <?php if ($block->getFieldValue('manage_stock') == 0) :?> + selected="selected" + <?php endif; ?>><?= $block->escapeHtml(__('No')) ?></option> </select> </div> <div class="field choice"> - <input name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_manage_stock]" type="checkbox" + <input name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_manage_stock]" type="checkbox" id="inventory_use_config_manage_stock" data-role="toggle-editability" value="1" checked="checked" disabled="disabled"/> <label for="inventory_use_config_manage_stock" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_manage_stock_checkbox" data-role="toggle-editability-all"/> <label for="inventory_manage_stock_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field required"> <label class="label" for="inventory_qty"> - <span><?= /* @escapeNotVerified */ __('Qty') ?></span> + <span><?= $block->escapeHtml(__('Qty')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text required-entry validate-number" id="inventory_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[qty]" - value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('qty') * 1 ?>" disabled="disabled"/> + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[qty]" + value="<?= $block->getDefaultConfigValue('qty') * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> <input type="checkbox" id="inventory_qty_checkbox" data-role="toggle-editability-all"/> <label for="inventory_qty_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field with-addon"> <label class="label" for="inventory_min_qty"> - <span><?= /* @escapeNotVerified */ __('Out-of-Stock Threshold') ?></span> + <span><?= $block->escapeHtml(__('Out-of-Stock Threshold')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text validate-number" id="inventory_min_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[min_qty]" - value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('min_qty') * 1 ?>" disabled="disabled"/> + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[min_qty]" + value="<?= $block->getDefaultConfigValue('min_qty') * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> <input type="checkbox" id="inventory_use_config_min_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_min_qty]" value="1" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_min_qty]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> <label for="inventory_use_config_min_qty" class="label"> - <span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span> + <span><?= $block->escapeHtml(__('Use Config Settings')) ?></span> </label> </div> <div class="field choice"> <input type="checkbox" id="inventory_min_qty_checkbox" data-role="toggle-editability-all"/> <label for="inventory_min_qty_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_min_sale_qty"> - <span><?= /* @escapeNotVerified */ __('Minimum Qty Allowed in Shopping Cart') ?></span> + <span><?= $block->escapeHtml(__('Minimum Qty Allowed in Shopping Cart')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text validate-number" id="inventory_min_sale_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[min_sale_qty]" - value="<?= /* @escapeNotVerified */ $defaultMinSaleQty ?>" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[min_sale_qty]" + value="<?= $defaultMinSaleQty * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> <input type="checkbox" id="inventory_use_config_min_sale_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_min_sale_qty]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_min_sale_qty]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_min_sale_qty" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_min_sale_qty_checkbox" data-role="toggle-editability-all"/> <label for="inventory_min_sale_qty_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_max_sale_qty"> - <span><?= /* @escapeNotVerified */ __('Maximum Qty Allowed in Shopping Cart') ?></span> + <span><?= $block->escapeHtml(__('Maximum Qty Allowed in Shopping Cart')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text validate-number" id="inventory_max_sale_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[max_sale_qty]" - value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('max_sale_qty') * 1 ?>" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[max_sale_qty]" + value="<?= $block->getDefaultConfigValue('max_sale_qty') * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> - <input type="checkbox" id="inventory_use_config_max_sale_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_max_sale_qty]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + <input type="checkbox" + id="inventory_use_config_max_sale_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_max_sale_qty]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_max_sale_qty" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_max_sale_checkbox" data-role="toggle-editability-all"/> <label for="inventory_max_sale_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_is_qty_decimal"> - <span><?= /* @escapeNotVerified */ __('Qty Uses Decimals') ?></span> + <span><?= $block->escapeHtml(__('Qty Uses Decimals')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <select id="inventory_is_qty_decimal" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[is_qty_decimal]" class="select" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[is_qty_decimal]" + class="select" disabled="disabled"> - <option value="0"><?= /* @escapeNotVerified */ __('No') ?></option> - <option - value="1"<?php if ($block->getDefaultConfigValue('is_qty_decimal') == 1): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('Yes') ?></option> + <option value="0"><?= $block->escapeHtml(__('No')) ?></option> + <option value="1" + <?php if ($block->getDefaultConfigValue('is_qty_decimal') == 1) :?> + selected="selected" + <?php endif; ?>><?= $block->escapeHtml(__('Yes')) ?></option> </select> </div> <div class="field choice"> <input type="checkbox" id="inventory_is_qty_decimal_checkbox" data-role="toggle-editability-all"/> <label for="inventory_is_qty_decimal_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_backorders"> - <span><?= /* @escapeNotVerified */ __('Backorders') ?></span> + <span><?= $block->escapeHtml(__('Backorders')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> - <select id="inventory_backorders" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[backorders]" - class="select" disabled="disabled"> - <?php foreach ($block->getBackordersOption() as $option): ?> + <select id="inventory_backorders" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[backorders]" + class="select" + disabled="disabled"> + <?php foreach ($block->getBackordersOption() as $option) :?> <?php $_selected = ($option['value'] == $block->getDefaultConfigValue('backorders')) ? ' selected="selected"' : '' ?> <option - value="<?= /* @escapeNotVerified */ $option['value'] ?>"<?= /* @escapeNotVerified */ $_selected ?>><?= /* @escapeNotVerified */ $option['label'] ?></option> + value="<?= $block->escapeHtmlAttr($option['value']) ?>"<?= /* @noEscape */ $_selected ?>><?= $block->escapeHtml($option['label']) ?></option> <?php endforeach; ?> </select> </div> <div class="field choice"> <input type="checkbox" id="inventory_use_config_backorders" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_backorders]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_backorders]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_backorders" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_backorders_checkbox" data-role="toggle-editability-all"/> - <label for="inventory_backorders_checkbox" class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + <label for="inventory_backorders_checkbox" + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_notify_stock_qty"> - <span><?= /* @escapeNotVerified */ __('Notify for Quantity Below') ?></span> + <span><?= $block->escapeHtml(__('Notify for Quantity Below')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text validate-number" id="inventory_notify_stock_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[notify_stock_qty]" - value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('notify_stock_qty') * 1 ?>" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[notify_stock_qty]" + value="<?= $block->getDefaultConfigValue('notify_stock_qty') * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> - <input type="checkbox" id="inventory_use_config_notify_stock_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_notify_stock_qty]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + <input type="checkbox" + id="inventory_use_config_notify_stock_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_notify_stock_qty]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_notify_stock_qty" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_notify_stock_qty_checkbox" data-role="toggle-editability-all"/> <label for="inventory_notify_stock_qty_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_enable_qty_increments"> - <span><?= /* @escapeNotVerified */ __('Enable Qty Increments') ?></span> + <span><?= $block->escapeHtml(__('Enable Qty Increments')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <select id="inventory_enable_qty_increments" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[enable_qty_increments]" class="select" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[enable_qty_increments]" + class="select" disabled="disabled"> - <option value="1"><?= /* @escapeNotVerified */ __('Yes') ?></option> - <option - value="0"<?php if ($block->getDefaultConfigValue('enable_qty_increments') == 0): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('No') ?></option> + <option value="1"><?= $block->escapeHtml(__('Yes')) ?></option> + <option value="0" + <?php if ($block->getDefaultConfigValue('enable_qty_increments') == 0) :?> + selected="selected" + <?php endif; ?>><?= $block->escapeHtml(__('No')) ?></option> </select> </div> <div class="field choice"> <input type="checkbox" id="inventory_use_config_enable_qty_increments" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_enable_qty_increments]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_enable_qty_increments]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_enable_qty_increments" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_enable_qty_increments_checkbox" data-role="toggle-editability-all"/> <label for="inventory_enable_qty_increments_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_qty_increments"> - <span><?= /* @escapeNotVerified */ __('Qty Increments') ?></span> + <span><?= $block->escapeHtml(__('Qty Increments')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <input type="text" class="input-text validate-number" id="inventory_qty_increments" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[qty_increments]" - value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('qty_increments') * 1 ?>" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[qty_increments]" + value="<?= $block->getDefaultConfigValue('qty_increments') * 1 ?>" disabled="disabled"/> </div> <div class="field choice"> - <input type="checkbox" id="inventory_use_config_qty_increments" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[use_config_qty_increments]" value="1" data-role="toggle-editability" checked="checked" disabled="disabled"/> + <input type="checkbox" + id="inventory_use_config_qty_increments" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[use_config_qty_increments]" + value="1" + data-role="toggle-editability" + checked="checked" + disabled="disabled"/> <label for="inventory_use_config_qty_increments" - class="label"><span><?= /* @escapeNotVerified */ __('Use Config Settings') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Use Config Settings')) ?></span></label> </div> <div class="field choice"> <input type="checkbox" id="inventory_qty_increments_checkbox" data-role="toggle-editability-all"/> <label for="inventory_qty_increments_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> <div class="field"> <label class="label" for="inventory_stock_availability"> - <span><?= /* @escapeNotVerified */ __('Stock Availability') ?></span> + <span><?= $block->escapeHtml(__('Stock Availability')) ?></span> </label> <div class="control"> <div class="fields-group-2"> <div class="field"> <select id="inventory_stock_availability" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[is_in_stock]" class="select" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[is_in_stock]" class="select" disabled="disabled"> - <option value="1"><?= /* @escapeNotVerified */ __('In Stock') ?></option> - <option - value="0"<?php if ($block->getDefaultConfigValue('is_in_stock') == 0): ?> selected<?php endif; ?>><?= /* @escapeNotVerified */ __('Out of Stock') ?></option> + <option value="1"><?= $block->escapeHtml(__('In Stock')) ?></option> + <option value="0"<?php if ($block->getDefaultConfigValue('is_in_stock') == 0) :?> selected<?php endif; ?>><?= $block->escapeHtml(__('Out of Stock')) ?></option> </select> </div> <div class="field choice"> <input type="checkbox" id="inventory_stock_availability_checkbox" data-role="toggle-editability-all"/> <label for="inventory_stock_availability_checkbox" - class="label"><span><?= /* @escapeNotVerified */ __('Change') ?></span></label> + class="label"><span><?= $block->escapeHtml(__('Change')) ?></span></label> </div> </div> </div> - <div class="field-service" value-scope="<?= /* @escapeNotVerified */ __('[GLOBAL]') ?>"></div> + <div class="field-service" value-scope="<?= $block->escapeHtmlAttr(__('[GLOBAL]')) ?>"></div> </div> </fieldset> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/websites.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/websites.phtml index cd297a7bbf27b..98b06050e0d1d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/websites.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/websites.phtml @@ -4,29 +4,35 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute\Tab\Websites */ ?> <div class="fieldset-wrapper" id="add-products-to-website-wrapper"> <fieldset class="fieldset" id="grop_fields"> <legend class="legend"> - <span><?= /* @escapeNotVerified */ __('Add Product To Websites') ?></span> + <span><?= $block->escapeHtml(__('Add Product To Websites')) ?></span> </legend> <br> <div class="store-scope"> <div class="store-tree" id="add-products-to-website-content"> - <?php foreach ($block->getWebsiteCollection() as $_website): ?> + <?php foreach ($block->getWebsiteCollection() as $_website) :?> <div class="website-name"> - <input name="add_website_ids[]" value="<?= /* @escapeNotVerified */ $_website->getId() ?>" <?php if ($block->getWebsitesReadonly()): ?>disabled="disabled"<?php endif;?> class="checkbox website-checkbox" id="add_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>" type="checkbox" /> - <label for="add_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>"><?= $block->escapeHtml($_website->getName()) ?></label> + <input name="add_website_ids[]" + value="<?= $block->escapeHtmlAttr($_website->getId()) ?>" + <?php if ($block->getWebsitesReadonly()) :?> + disabled="disabled" + <?php endif;?> + class="checkbox website-checkbox" + id="add_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>" + type="checkbox" /> + <label for="add_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>"><?= $block->escapeHtml($_website->getName()) ?></label> </div> - <dl class="webiste-groups" id="add_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>_data"> - <?php foreach ($block->getGroupCollection($_website) as $_group): ?> + <dl class="webiste-groups" id="add_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>_data"> + <?php foreach ($block->getGroupCollection($_website) as $_group) :?> <dt><?= $block->escapeHtml($_group->getName()) ?></dt> <dd class="group-stores"> <ul> - <?php foreach ($block->getStoreCollection($_group) as $_store): ?> + <?php foreach ($block->getStoreCollection($_group) as $_store) :?> <li> <?= $block->escapeHtml($_store->getName()) ?> </li> @@ -44,27 +50,35 @@ <div class="fieldset-wrapper" id="remove-products-to-website-wrapper"> <fieldset class="fieldset" id="grop_fields"> <legend class="legend"> - <span><?= /* @escapeNotVerified */ __('Remove Product From Websites') ?></span> + <span><?= $block->escapeHtml(__('Remove Product From Websites')) ?></span> </legend> <br> <div class="messages"> <div class="message message-notice"> - <div><?= /* @escapeNotVerified */ __('To hide an item in catalog or search results, set the status to "Disabled".') ?></div> + <div><?= $block->escapeHtml(__('To hide an item in catalog or search results, set the status to "Disabled".')) ?></div> </div> </div> <div class="store-scope"> <div class="store-tree" id="remove-products-to-website-content"> - <?php foreach ($block->getWebsiteCollection() as $_website): ?> + <?php foreach ($block->getWebsiteCollection() as $_website) :?> <div class="website-name"> - <input name="remove_website_ids[]" value="<?= /* @escapeNotVerified */ $_website->getId() ?>" <?php if ($block->getWebsitesReadonly()): ?>disabled="disabled"<?php endif;?> class="checkbox website-checkbox" id="remove_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>" type="checkbox" /> - <label for="remove_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>"><?= $block->escapeHtml($_website->getName()) ?></label> + <input name="remove_website_ids[]" + value="<?= $block->escapeHtmlAttr($_website->getId()) ?>" + <?php if ($block->getWebsitesReadonly()) :?> + disabled="disabled" + <?php endif;?> + class="checkbox website-checkbox" + id="remove_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>" + type="checkbox" /> + <label for="remove_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>"><?= $block->escapeHtml($_website->getName()) ?></label> </div> - <dl class="webiste-groups" id="remove_product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>_data"> - <?php foreach ($block->getGroupCollection($_website) as $_group): ?> + <dl class="webiste-groups" + id="remove_product_website_<?= $block->escapeHtmlAttr($_website->getId()) ?>_data"> + <?php foreach ($block->getGroupCollection($_website) as $_group) :?> <dt><?= $block->escapeHtml($_group->getName()) ?></dt> <dd class="group-stores"> <ul> - <?php foreach ($block->getStoreCollection($_group) as $_store): ?> + <?php foreach ($block->getStoreCollection($_group) as $_store) :?> <li> <?= $block->escapeHtml($_store->getName()) ?> </li> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml index a7e8564e7a1d8..d073053e2f854 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml @@ -4,9 +4,9 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis - /* @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\AttributeSet */ +/* @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\AttributeSet */ ?> <script id="product-template-selector-template" type="text/x-magento-template"> <% if (!data.term && data.items.length && !data.allShown()) { %> @@ -32,7 +32,7 @@ } }); $suggest - .mage('suggest',<?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getSelectorOptions()) ?>) + .mage('suggest',<?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getSelectorOptions()) ?>) .on('suggestselect', function (e, ui) { if (ui.item.id) { $('[data-form=edit-product]').trigger('changeAttributeSet', ui.item); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/category/new/form.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/category/new/form.phtml index 84c3257840259..f12a99e6c7843 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/category/new/form.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/category/new/form.phtml @@ -5,7 +5,7 @@ */ /* @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\NewCategory */ ?> -<div id="<?= /* @escapeNotVerified */ $block->getNameInLayout() ?>" style="display:none"> +<div id="<?= $block->escapeHtmlAttr($block->getNameInLayout()) ?>" style="display:none"> <?= $block->getFormHtml() ?> <?= $block->getAfterElementHtml() ?> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml index 2570a5d712675..ad38d250a3345 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml @@ -3,16 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options */ ?> <div class="fieldset-wrapper" id="product-custom-options-wrapper" data-block="product-custom-options"> <div class="fieldset-wrapper-title"> <strong class="title"> - <span><?= /* @escapeNotVerified */ __('Custom Options') ?></span> + <span><?= $block->escapeHtml(__('Custom Options')) ?></span> </strong> </div> <div class="fieldset-wrapper-content" id="product-custom-options-content" data-role="product-custom-options-content"> @@ -20,7 +17,7 @@ <div class="messages"> <div class="message message-error" id="dynamic-price-warning" style="display: none;"> <div class="message-inner"> - <div class="message-content"><?= /* @escapeNotVerified */ __('We can\'t save custom-defined options for bundles with dynamic pricing.') ?></div> + <div class="message-content"><?= $block->escapeHtml(__('We can\'t save custom-defined options for bundles with dynamic pricing.')) ?></div> </div> </div> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml index d2bca5ce17321..713366e73aba5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml @@ -4,8 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option */ ?> <?= $block->getTemplatesHtml() ?> @@ -19,30 +18,52 @@ <span id="option_<%- data.id %>_header_title"><%- data.title %></span> </strong> <div class="actions"> - <button type="button" title="<?= /* @escapeNotVerified */ __('Delete Custom Option') ?>" class="action-delete" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_delete"> - <span><?= /* @escapeNotVerified */ __('Delete Custom Option') ?></span> + <button type="button" + title="<?= $block->escapeHtmlAttr(__('Delete Custom Option')) ?>" + class="action-delete" + id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_delete"> + <span><?= $block->escapeHtml(__('Delete Custom Option')) ?></span> </button> </div> - <div id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_move" data-role="draggable-handle" class="draggable-handle" - title="<?= /* @escapeNotVerified */ __('Sort Custom Options') ?>"></div> + <div id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_move" + data-role="draggable-handle" + class="draggable-handle" + title="<?= $block->escapeHtmlAttr(__('Sort Custom Options')) ?>"></div> </div> <div class="fieldset-wrapper-content in collapse" id="<%- data.id %>-content"> <fieldset class="fieldset"> - <fieldset class="fieldset-alt" id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>"> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_is_delete" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][is_delete]" type="hidden" value=""/> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_previous_type" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][previous_type]" type="hidden" value="<%- data.type %>"/> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_previous_group" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][previous_group]" type="hidden" value=""/> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_id" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][id]" type="hidden" value="<%- data.id %>"/> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_option_id" name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][option_id]" type="hidden" value="<%- data.option_id %>"/> - <input name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][sort_order]" type="hidden" value="<%- data.sort_order %>"/> + <fieldset class="fieldset-alt" id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>"> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_is_delete" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][is_delete]" + type="hidden" + value=""/> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_previous_type" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][previous_type]" + type="hidden" + value="<%- data.type %>"/> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_previous_group" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][previous_group]" + type="hidden" + value=""/> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_id" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][id]" + type="hidden" + value="<%- data.id %>"/> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_option_id" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][option_id]" + type="hidden" + value="<%- data.option_id %>"/> + <input name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][sort_order]" + type="hidden" + value="<%- data.sort_order %>"/> <div class="field field-option-title required"> - <label class="label" for="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_title"> - <?= /* @escapeNotVerified */ __('Option Title') ?> + <label class="label" for="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_title"> + <?= $block->escapeHtml(__('Option Title')) ?> </label> <div class="control"> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_title" - name="<?= /* @escapeNotVerified */ $block->getFieldName() ?>[<%- data.id %>][title]" + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_title" + name="<?= /* @noEscape */ $block->getFieldName() ?>[<%- data.id %>][title]" class="required-entry input-text" type="text" value="<%- data.title %>" @@ -54,8 +75,8 @@ </div> <div class="field field-option-input-type required"> - <label class="label" for="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_title"> - <?= /* @escapeNotVerified */ __('Input Type') ?> + <label class="label" for="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_title"> + <?= $block->escapeHtml(__('Input Type')) ?> </label> <div class="control opt-type"> <?= $block->getTypeSelectHtml() ?> @@ -64,9 +85,12 @@ <div class="field field-option-req"> <div class="control"> - <input id="<?= /* @escapeNotVerified */ $block->getFieldId() ?>_<%- data.id %>_required" class="is-required" type="checkbox" checked="checked"/> + <input id="<?= /* @noEscape */ $block->getFieldId() ?>_<%- data.id %>_required" + class="is-required" + type="checkbox" + checked="checked"/> <label for="field-option-req"> - <?= /* @escapeNotVerified */ __('Required') ?> + <?= $block->escapeHtml(__('Required')) ?> </label> <span style="display:none"><?= $block->getRequireSelectHtml() ?></span> </div> @@ -78,7 +102,7 @@ </script> <div id="import-container" style="display: none;"></div> -<?php if (!$block->isReadonly()): ?> +<?php if (!$block->isReadonly()) :?> <div><input type="hidden" name="affect_product_custom_options" value="1"/></div> <?php endif; ?> <script> @@ -89,21 +113,21 @@ require([ jQuery(function ($) { var fieldSet = $('[data-block=product-custom-options]'); - fieldSet.customOptions(<?php /* @escapeNotVerified */ echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode( + fieldSet.customOptions(<?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode( [ 'fieldId' => $block->getFieldId(), - 'productGridUrl' => $block->getProductGridUrl(), + 'productGridUrl' => $block->escapeUrl($block->getProductGridUrl()), 'formKey' => $block->getFormKey(), - 'customOptionsUrl' => $block->getCustomOptionsUrl(), - 'isReadonly' => $block->isReadonly(), - 'itemCount' => $block->getItemCount(), - 'currentProductId' => $block->getCurrentProductId(), + 'customOptionsUrl' => $block->escapeUrl($block->getCustomOptionsUrl()), + 'isReadonly' => (bool) $block->isReadonly(), + 'itemCount' => (int) $block->getItemCount(), + 'currentProductId' => (int) $block->getCurrentProductId(), ] )?>); //adding data to templates <?php /** @var $_value \Magento\Framework\DataObject */ ?> - <?php foreach ($block->getOptionValues() as $_value): ?> - fieldSet.customOptions('addOption', <?= /* @escapeNotVerified */ $_value->toJson() ?>); + <?php foreach ($block->getOptionValues() as $_value) :?> + fieldSet.customOptions('addOption', <?= /* @noEscape */ $_value->toJson() ?>); <?php endforeach; ?> }); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml index 07ce6e5d86256..2063609bf0568 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Type\Date */ ?> <script id="custom-option-date-type-template" type="text/x-magento-template"> @@ -14,10 +12,10 @@ <thead> <tr class="headings"> <?php if ($block->getCanReadPrice() !== false) : ?> - <th><?= /* @escapeNotVerified */ __('Price') ?></th> - <th><?= /* @escapeNotVerified */ __('Price Type') ?></th> + <th><?= $block->escapeHtml(__('Price')) ?></th> + <th><?= $block->escapeHtml(__('Price Type')) ?></th> <?php endif; ?> - <th><?= /* @escapeNotVerified */ __('SKU') ?></th> + <th><?= $block->escapeHtml(__('SKU')) ?></th> </tr> </thead> <tr> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml index 693c98fc02cab..c0e61c5de9988 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Type\File */ ?> <script id="custom-option-file-type-template" type="text/x-magento-template"> @@ -14,27 +12,27 @@ <thead> <tr> <?php if ($block->getCanReadPrice() !== false) : ?> - <th><?= /* @escapeNotVerified */ __('Price') ?></th> - <th><?= /* @escapeNotVerified */ __('Price Type') ?></th> + <th><?= $block->escapeHtml(__('Price')) ?></th> + <th><?= $block->escapeHtml(__('Price Type')) ?></th> <?php endif; ?> - <th><?= /* @escapeNotVerified */ __('SKU') ?></th> - <th><?= /* @escapeNotVerified */ __('Compatible File Extensions') ?></th> - <th><?= /* @escapeNotVerified */ __('Maximum Image Size') ?></th> + <th><?= $block->escapeHtml(__('SKU')) ?></th> + <th><?= $block->escapeHtml(__('Compatible File Extensions')) ?></th> + <th><?= $block->escapeHtml(__('Maximum Image Size')) ?></th> </tr> </thead> <tr> <?php if ($block->getCanReadPrice() !== false) : ?> - <td class="opt-price"> - <input name="product[options][<%- data.option_id %>][price]" data-store-label="<%- data.price %>" - class="input-text validate-zero-or-greater" type="text" value="<%- data.price %>" - <?php if ($block->getCanEditPrice() === false) : ?> - disabled="disabled" - <?php endif; ?>> - </td> - <td class="opt-price-type"><?= $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %></td> + <td class="opt-price"> + <input name="product[options][<%- data.option_id %>][price]" data-store-label="<%- data.price %>" + class="input-text validate-zero-or-greater" type="text" value="<%- data.price %>" + <?php if ($block->getCanEditPrice() === false) : ?> + disabled="disabled" + <?php endif; ?>> + </td> + <td class="opt-price-type"><?= $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %></td> <?php else : ?> - <input name="product[options][<%- data.option_id %>][price]" type="hidden"> - <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> + <input name="product[options][<%- data.option_id %>][price]" type="hidden"> + <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> <?php endif; ?> <td> <input name="product[options][<%- data.option_id %>][sku]" class="input-text" type="text" value="<%- data.sku %>"> @@ -42,10 +40,15 @@ <td> <input name="product[options][<%- data.option_id %>][file_extension]" class="input-text" type="text" value="<%- data.file_extension %>"> </td> - <td class="col-file"><?php /* @escapeNotVerified */ echo __('%1 <span>x</span> %2 <span>px.</span>', - '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_x]" value="<%- data.image_size_x %>">', - '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_y]" value="<%- data.image_size_y %>">') ?> - <div class="note"><?= /* @escapeNotVerified */ __('Please leave blank if it is not an image.') ?></div> + <td class="col-file"><?= $block->escapeHtml( + __( + '%1 <span>x</span> %2 <span>px.</span>', + '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_x]" value="<%- data.image_size_x %>">', + '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_y]" value="<%- data.image_size_y %>">' + ), + ['span', 'input'] + ) ?> + <div class="note"><?= $block->escapeHtml(__('Please leave blank if it is not an image.')) ?></div> </td> </tr> </table> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml index e8c398228a469..c7ff03a08d954 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Type\Select */ ?> <script id="custom-option-select-type-template" type="text/x-magento-template"> @@ -14,12 +12,12 @@ <thead> <tr> <th class="col-draggable"> </th> - <th class="col-name required"><?= /* @escapeNotVerified */ __('Title') ?><span class="required">*</span></th> + <th class="col-name required"><?= $block->escapeHtml(__('Title')) ?><span class="required">*</span></th> <?php if ($block->getCanReadPrice() !== false) : ?> - <th class="col-price"><?= /* @escapeNotVerified */ __('Price') ?></th> - <th class="col-price-type"><?= /* @escapeNotVerified */ __('Price Type') ?></th> + <th class="col-price"><?= $block->escapeHtml(__('Price')) ?></th> + <th class="col-price-type"><?= $block->escapeHtml(__('Price Type')) ?></th> <?php endif; ?> - <th class="col-sku"><?= /* @escapeNotVerified */ __('SKU') ?></th> + <th class="col-sku"><?= $block->escapeHtml(__('SKU')) ?></th> <th class="col-actions"> </th> </tr> </thead> @@ -38,7 +36,7 @@ <tr id="product_option_<%- data.id %>_select_<%- data.select_id %>"> <td class="col-draggable"> <div data-role="draggable-handle" class="draggable-handle" - title="<?= /* @escapeNotVerified */ __('Sort Custom Option') ?>"></div> + title="<?= $block->escapeHtmlAttr(__('Sort Custom Option')) ?>"></div> <input name="product[options][<%- data.id %>][values][<%- data.select_id %>][sort_order]" type="hidden" value="<%- data.sort_order %>"> </td> <td class="col-name select-opt-title"> @@ -58,7 +56,7 @@ <?php endif; ?>> </td> <td class="col-price-type select-opt-price-type"> - <?= /* @escapeNotVerified */ $block->getPriceTypeSelectHtml('data-attr="price-type" <% if (typeof data.scopePriceDisabled != "undefined" && data.scopePriceDisabled != null) { %> disabled="disabled" <% } %>') ?><%- data.checkboxScopePrice %> + <?= /* @noEscape */ $block->getPriceTypeSelectHtml('data-attr="price-type" <% if (typeof data.scopePriceDisabled != "undefined" && data.scopePriceDisabled != null) { %> disabled="disabled" <% } %>') ?><%- data.checkboxScopePrice %> </td> <?php else : ?> <input id="product_option_<%- data.id %>_select_<%- data.select_id %>_price" name="product[options][<%- data.id %>][values][<%- data.select_id %>][price]" type="hidden"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml index c9d7190589ff5..89da5d633ef4d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Type\Text */ ?> <script id="custom-option-text-type-template" type="text/x-magento-template"> @@ -14,11 +12,11 @@ <thead> <tr> <?php if ($block->getCanReadPrice() !== false) : ?> - <th class="type-price"><?= /* @escapeNotVerified */ __('Price') ?></th> - <th class="type-type"><?= /* @escapeNotVerified */ __('Price Type') ?></th> + <th class="type-price"><?= $block->escapeHtml(__('Price')) ?></th> + <th class="type-type"><?= $block->escapeHtml(__('Price Type')) ?></th> <?php endif; ?> - <th class="type-sku"><?= /* @escapeNotVerified */ __('SKU') ?></th> - <th class="type-last last"><?= /* @escapeNotVerified */ __('Max Characters') ?></th> + <th class="type-sku"><?= $block->escapeHtml(__('SKU')) ?></th> + <th class="type-last last"><?= $block->escapeHtml(__('Max Characters')) ?></th> </tr> </thead> <tr> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/tier.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/tier.phtml index 57715744823d6..e66a18c677cc3 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/tier.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/tier.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /* @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Tier */ $element = $block->getElement(); @@ -20,28 +20,28 @@ $element = $block->getElement(); <?php $_showWebsite = $block->isShowWebsiteColumn(); ?> <?php $_showWebsite = $block->isMultiWebsites(); ?> -<div class="field" id="attribute-<?= /* @escapeNotVerified */ $_htmlId ?>-container" data-attribute-code="<?= /* @escapeNotVerified */ $_htmlId ?>" +<div class="field" id="attribute-<?= /* @noEscape */ $_htmlId ?>-container" data-attribute-code="<?= /* @noEscape */ $_htmlId ?>" data-apply-to="<?= $block->escapeHtml( - $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode( + $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode( $element->hasEntityAttribute() ? $element->getEntityAttribute()->getApplyTo() : [] ) )?>"> - <label class="label"><span><?= /* @escapeNotVerified */ $block->getElement()->getLabel() ?></span></label> + <label class="label"><span><?= $block->escapeHtml($block->getElement()->getLabel()) ?></span></label> <div class="control"> <table class="admin__control-table tiers_table" id="tiers_table"> <thead> <tr> - <th class="col-websites" <?php if (!$_showWebsite): ?>style="display:none"<?php endif; ?>><?= /* @escapeNotVerified */ __('Web Site') ?></th> - <th class="col-customer-group"><?= /* @escapeNotVerified */ __('Customer Group') ?></th> - <th class="col-qty required"><?= /* @escapeNotVerified */ __('Quantity') ?></th> - <th class="col-price required"><?= /* @escapeNotVerified */ $block->getPriceColumnHeader(__('Item Price')) ?></th> - <th class="col-delete"><?= /* @escapeNotVerified */ __('Action') ?></th> + <th class="col-websites" <?php if (!$_showWebsite) :?>style="display:none"<?php endif; ?>><?= $block->escapeHtml(__('Web Site')) ?></th> + <th class="col-customer-group"><?= $block->escapeHtml(__('Customer Group')) ?></th> + <th class="col-qty required"><?= $block->escapeHtml(__('Quantity')) ?></th> + <th class="col-price required"><?= $block->escapeHtml($block->getPriceColumnHeader(__('Item Price'))) ?></th> + <th class="col-delete"><?= $block->escapeHtml(__('Action')) ?></th> </tr> </thead> - <tbody id="<?= /* @escapeNotVerified */ $_htmlId ?>_container"></tbody> + <tbody id="<?= /* @noEscape */ $_htmlId ?>_container"></tbody> <tfoot> <tr> - <td colspan="<?php if (!$_showWebsite): ?>4<?php else: ?>5<?php endif; ?>" class="col-actions-add"><?= $block->getAddButtonHtml() ?></td> + <td colspan="<?php if (!$_showWebsite) :?>4<?php else :?>5<?php endif; ?>" class="col-actions-add"><?= $block->getAddButtonHtml() ?></td> </tr> </tfoot> </table> @@ -55,39 +55,39 @@ require([ //<![CDATA[ var tierPriceRowTemplate = '<tr>' - + '<td class="col-websites"<?php if (!$_showWebsite): ?> style="display:none"<?php endif; ?>>' - + '<select class="<?= /* @escapeNotVerified */ $_htmlClass ?> required-entry" name="<?= /* @escapeNotVerified */ $_htmlName ?>[<%- data.index %>][website_id]" id="tier_price_row_<%- data.index %>_website">' - <?php foreach ($block->getWebsites() as $_websiteId => $_info): ?> - + '<option value="<?= /* @escapeNotVerified */ $_websiteId ?>"><?= $block->escapeJs($_info['name']) ?><?php if (!empty($_info['currency'])): ?> [<?= $block->escapeHtml($_info['currency']) ?>]<?php endif; ?></option>' + + '<td class="col-websites"<?php if (!$_showWebsite) :?> style="display:none"<?php endif; ?>>' + + '<select class="<?= $block->escapeHtmlAttr($_htmlClass) ?> required-entry" name="<?= /* @noEscape */ $_htmlName ?>[<%- data.index %>][website_id]" id="tier_price_row_<%- data.index %>_website">' + <?php foreach ($block->getWebsites() as $_websiteId => $_info) :?> + + '<option value="<?= $block->escapeHtmlAttr($_websiteId) ?>"><?= $block->escapeHtml($_info['name']) ?><?php if (!empty($_info['currency'])) :?> [<?= $block->escapeHtml($_info['currency']) ?>]<?php endif; ?></option>' <?php endforeach ?> + '</select></td>' - + '<td class="col-customer-group"><select class="<?= /* @escapeNotVerified */ $_htmlClass ?> custgroup required-entry" name="<?= /* @escapeNotVerified */ $_htmlName ?>[<%- data.index %>][cust_group]" id="tier_price_row_<%- data.index %>_cust_group">' - <?php foreach ($block->getCustomerGroups() as $_groupId => $_groupName): ?> - + '<option value="<?= /* @escapeNotVerified */ $_groupId ?>"><?= $block->escapeJs($_groupName) ?></option>' + + '<td class="col-customer-group"><select class="<?= $block->escapeHtmlAttr($_htmlClass) ?> custgroup required-entry" name="<?= /* @noEscape */ $_htmlName ?>[<%- data.index %>][cust_group]" id="tier_price_row_<%- data.index %>_cust_group">' + <?php foreach ($block->getCustomerGroups() as $_groupId => $_groupName) :?> + + '<option value="<?= $block->escapeHtmlAttr($_groupId) ?>"><?= $block->escapeHtml($_groupName) ?></option>' <?php endforeach ?> + '</select></td>' + '<td class="col-qty">' - + '<input class="<?= /* @escapeNotVerified */ $_htmlClass ?> qty required-entry validate-greater-than-zero" type="text" name="<?= /* @escapeNotVerified */ $_htmlName ?>[<%- data.index %>][price_qty]" value="<%- data.qty %>" id="tier_price_row_<%- data.index %>_qty" />' - + '<span><?= /* @escapeNotVerified */ __("and above") ?></span>' + + '<input class="<?= $block->escapeHtmlAttr($_htmlClass) ?> qty required-entry validate-greater-than-zero" type="text" name="<?= /* @noEscape */ $_htmlName ?>[<%- data.index %>][price_qty]" value="<%- data.qty %>" id="tier_price_row_<%- data.index %>_qty" />' + + '<span><?= $block->escapeHtml(__("and above")) ?></span>' + '</td>' - + '<td class="col-price"><input class="<?= /* @escapeNotVerified */ $_htmlClass ?> required-entry <?= /* @escapeNotVerified */ $_priceValueValidation ?>" type="text" name="<?= /* @escapeNotVerified */ $_htmlName ?>[<%- data.index %>][price]" value="<%- data.price %>" id="tier_price_row_<%- data.index %>_price" /></td>' - + '<td class="col-delete"><input type="hidden" name="<?= /* @escapeNotVerified */ $_htmlName ?>[<%- data.index %>][delete]" class="delete" value="" id="tier_price_row_<%- data.index %>_delete" />' - + '<button title="<?= /* @escapeNotVerified */ $block->escapeHtml(__('Delete Tier')) ?>" type="button" class="action- scalable delete icon-btn delete-product-option" id="tier_price_row_<%- data.index %>_delete_button" onclick="return tierPriceControl.deleteItem(event);">' - + '<span><?= /* @escapeNotVerified */ __("Delete") ?></span></button></td>' + + '<td class="col-price"><input class="<?= $block->escapeHtmlAttr($_htmlClass) ?> required-entry <?= $block->escapeHtmlAttr($_priceValueValidation) ?>" type="text" name="<?= /* @noEscape */ $_htmlName ?>[<%- data.index %>][price]" value="<%- data.price %>" id="tier_price_row_<%- data.index %>_price" /></td>' + + '<td class="col-delete"><input type="hidden" name="<?= /* @noEscape */ $_htmlName ?>[<%- data.index %>][delete]" class="delete" value="" id="tier_price_row_<%- data.index %>_delete" />' + + '<button title="<?= $block->escapeHtml(__('Delete Tier')) ?>" type="button" class="action- scalable delete icon-btn delete-product-option" id="tier_price_row_<%- data.index %>_delete_button" onclick="return tierPriceControl.deleteItem(event);">' + + '<span><?= $block->escapeHtml(__("Delete")) ?></span></button></td>' + '</tr>'; var tierPriceControl = { template: mageTemplate(tierPriceRowTemplate), itemsCount: 0, addItem : function () { - <?php if ($_readonly): ?> + <?php if ($_readonly) :?> if (arguments.length < 4) { return; } <?php endif; ?> var data = { - website_id: '<?= /* @escapeNotVerified */ $block->getDefaultWebsite() ?>', - group: '<?= /* @escapeNotVerified */ $block->getDefaultCustomerGroup() ?>', + website_id: '<?= (int) $block->getDefaultWebsite() ?>', + group: '<?= (int) $block->getDefaultCustomerGroup() ?>', qty: '', price: '', readOnly: false, @@ -104,7 +104,7 @@ var tierPriceControl = { data.readOnly = arguments[4]; } - Element.insert($('<?= /* @escapeNotVerified */ $_htmlId ?>_container'), { + Element.insert($('<?= $block->escapeJs($_htmlId) ?>_container'), { bottom : this.template({ data: data }) @@ -113,7 +113,7 @@ var tierPriceControl = { $('tier_price_row_' + data.index + '_cust_group').value = data.group; $('tier_price_row_' + data.index + '_website').value = data.website_id; - <?php if ($block->isShowWebsiteColumn() && !$block->isAllowChangeWebsite()):?> + <?php if ($block->isShowWebsiteColumn() && !$block->isAllowChangeWebsite()) :?> var wss = $('tier_price_row_' + data.index + '_website'); var txt = wss.options[wss.selectedIndex].text; @@ -128,11 +128,11 @@ var tierPriceControl = { $('tier_price_row_'+data.index+'_delete_button').hide(); } - <?php if ($_readonly): ?> - $('<?= /* @escapeNotVerified */ $_htmlId ?>_container').select('input', 'select').each(this.disableElement); - $('<?= /* @escapeNotVerified */ $_htmlId ?>_container').up('table').select('button').each(this.disableElement); - <?php else: ?> - $('<?= /* @escapeNotVerified */ $_htmlId ?>_container').select('input', 'select').each(function(el){ Event.observe(el, 'change', el.setHasChanges.bind(el)); }); + <?php if ($_readonly) :?> + $('<?= $block->escapeJs($_htmlId) ?>_container').select('input', 'select').each(this.disableElement); + $('<?= $block->escapeJs($_htmlId) ?>_container').up('table').select('button').each(this.disableElement); + <?php else :?> + $('<?= $block->escapeJs($_htmlId) ?>_container').select('input', 'select').each(function(el){ Event.observe(el, 'change', el.setHasChanges.bind(el)); }); <?php endif; ?> }, disableElement: function(el) { @@ -150,11 +150,11 @@ var tierPriceControl = { return false; } }; -<?php foreach ($block->getValues() as $_item): ?> -tierPriceControl.addItem('<?= /* @escapeNotVerified */ $_item['website_id'] ?>', '<?= /* @escapeNotVerified */ $_item['cust_group'] ?>', '<?= /* @escapeNotVerified */ $_item['price_qty']*1 ?>', '<?= /* @escapeNotVerified */ $_item['price'] ?>', <?= (int)!empty($_item['readonly']) ?>); +<?php foreach ($block->getValues() as $_item) :?> +tierPriceControl.addItem('<?= $block->escapeJs($_item['website_id']) ?>', '<?= $block->escapeJs($_item['cust_group']) ?>', '<?= $_item['price_qty']*1 ?>', '<?= $block->escapeJs($_item['price']) ?>', <?= (int)!empty($_item['readonly']) ?>); <?php endforeach; ?> -<?php if ($_readonly): ?> -$('<?= /* @escapeNotVerified */ $_htmlId ?>_container').up('table').select('button') +<?php if ($_readonly) :?> +$('<?= $block->escapeJs($_htmlId) ?>_container').up('table').select('button') .each(tierPriceControl.disableElement); <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/serializer.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/serializer.phtml index 44fdb75cdac21..0c1da98c7d85a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/serializer.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/serializer.phtml @@ -4,9 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Ajax\Serializer */ ?> +// phpcs:disable Magento2.Security.InsecureFunction.DiscouragedWithAlternative <?php $_id = 'id_' . md5(microtime()) ?> -<input type="hidden" name="<?= /* @escapeNotVerified */ $block->getInputElementName() ?>" value="" id="<?= /* @escapeNotVerified */ $_id ?>" /> +<input type="hidden" + name="<?= $block->escapeHtmlAttr($block->getInputElementName()) ?>" + value="" + id="<?= /* @noEscape */ $_id ?>" /> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/websites.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/websites.phtml index 8f7f20f32d982..0193d7764cbb5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/websites.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/websites.phtml @@ -4,16 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Websites */ ?> <fieldset id="grop_fields" class="fieldset"> - <legend class="legend"><span><?= /* @escapeNotVerified */ __('Product In Websites') ?></span></legend> + <legend class="legend"><span><?= $block->escapeHtml(__('Product In Websites')) ?></span></legend> <br> - <?php if ($block->getProductId()): ?> + <?php if ($block->getProductId()) :?> <div class="messages"> <div class="message message-notice"> - <?= /* @escapeNotVerified */ __('To hide an item in catalog or search results, set the status to "Disabled".') ?> + <?= $block->escapeHtml(__('To hide an item in catalog or search results, set the status to "Disabled".')) ?> </div> </div> <?php endif; ?> @@ -21,22 +20,36 @@ <?= $block->getHintHtml() ?> <div class="store-tree"> <?php $_websites = $block->getWebsiteCollection() ?> - <?php foreach ($_websites as $_website): ?> + <?php foreach ($_websites as $_website) :?> <div class="website-name"> - <input name="product[website_ids][]" value="<?= /* @escapeNotVerified */ $_website->getId() ?>" <?php if ($block->isReadonly()): ?> disabled="disabled"<?php endif;?> class="checkbox website-checkbox" id="product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>" type="checkbox"<?php if ($block->hasWebsite($_website->getId()) || !$block->getProductId() && count($_websites) === 1): ?> checked="checked"<?php endif; ?> /> - <label for="product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>"><?= $block->escapeHtml($_website->getName()) ?></label> + <input name="product[website_ids][]" + value="<?= (int) $_website->getId() ?>" + <?php if ($block->isReadonly()) :?> + disabled="disabled" + <?php endif;?> + class="checkbox website-checkbox" + id="product_website_<?= (int) $_website->getId() ?>" + type="checkbox" + <?php if ($block->hasWebsite($_website->getId()) || !$block->getProductId() && count($_websites) === 1) :?> + checked="checked" + <?php endif; ?> + /> + <label for="product_website_<?= (int) $_website->getId() ?>"><?= $block->escapeHtml($_website->getName()) ?></label> </div> - <dl class="webiste-groups" id="product_website_<?= /* @escapeNotVerified */ $_website->getId() ?>_data"> - <?php foreach ($block->getGroupCollection($_website) as $_group): ?> + <dl class="webiste-groups" id="product_website_<?= (int) $_website->getId() ?>_data"> + <?php foreach ($block->getGroupCollection($_website) as $_group) :?> <dt><?= $block->escapeHtml($_group->getName()) ?></dt> <dd> <ul> - <?php foreach ($block->getStoreCollection($_group) as $_store): ?> + <?php foreach ($block->getStoreCollection($_group) as $_store) :?> <li> <?= $block->escapeHtml($_store->getName()) ?> - <?php if ($block->getWebsites() && !$block->hasWebsite($_website->getId())): ?> - <span class="website-<?= /* @escapeNotVerified */ $_website->getId() ?>-select" style="display:none"> - <?= __('(Copy data from: %1)', $block->getChooseFromStoreHtml($_store)) ?> + <?php if ($block->getWebsites() && !$block->hasWebsite($_website->getId())) :?> + <span class="website-<?= (int) $_website->getId() ?>-select" style="display:none"> + <?= $block->escapeHtml( + __('(Copy data from: %1)', $block->getChooseFromStoreHtml($_store)), + ['select', 'option', 'optgroup'] + ) ?> </span> <?php endif; ?> </li> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml index 574c9ee81af7d..ca77aa28ea667 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/helper/gallery.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content */ $elementName = $block->getElement()->getName() . '[images]'; @@ -16,61 +16,60 @@ $formName = $block->getFormName(); data-parent-component="<?= $block->escapeHtml($block->getData('config/parentComponent')) ?>" data-images="<?= $block->escapeHtml($block->getImagesJson()) ?>" data-types="<?= $block->escapeHtml( - $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes()) + $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getImageTypes()) ) ?>" - > +> <?php if (!$block->getElement()->getReadonly()) {?> <div class="image image-placeholder"> <?= $block->getUploaderHtml() ?> <div class="product-image-wrapper"> <p class="image-placeholder-text"> - <?= /* @escapeNotVerified */ __('Browse to find or drag image here') ?> + <?= $block->escapeHtml(__('Browse to find or drag image here')) ?> </p> </div> </div> <?php } ?> <?php foreach ($block->getImageTypes() as $typeData) { - ?> - <input name="<?= $block->escapeHtml($typeData['name']) ?>" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" - class="image-<?= $block->escapeHtml($typeData['code']) ?>" + ?> + <input name="<?= $block->escapeHtmlAttr($typeData['name']) ?>" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" + class="image-<?= $block->escapeHtmlAttr($typeData['code']) ?>" type="hidden" - value="<?= $block->escapeHtml($typeData['value']) ?>"/> - <?php - + value="<?= $block->escapeHtmlAttr($typeData['value']) ?>"/> + <?php } ?> <script id="<?= $block->getHtmlId() ?>-template" type="text/x-magento-template"> <div class="image item<% if (data.disabled == 1) { %> hidden-for-front<% } %>" data-role="image"> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][position]" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][position]" value="<%- data.position %>" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" class="position"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][file]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][file]" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" value="<%- data.file %>"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][value_id]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][value_id]" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" value="<%- data.value_id %>"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][label]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][label]" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" value="<%- data.label %>"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][disabled]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][disabled]" + data-form-part="<?= $block->escapeHtmlAttr(formName) ?>" value="<%- data.disabled %>"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][media_type]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][media_type]" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" value="image"/> <input type="hidden" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][removed]" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][removed]" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" value="" class="is-removed"/> @@ -84,21 +83,21 @@ $formName = $block->getFormName(); <button type="button" class="action-remove" data-role="delete-button" - title="<?= /* @escapeNotVerified */ __('Delete image') ?>"> + title="<?= $block->escapeHtmlAttr(__('Delete image')) ?>"> <span> - <?= /* @escapeNotVerified */ __('Delete image') ?> + <?= $block->escapeHtml(__('Delete image')) ?> </span> </button> <div class="draggable-handle"></div> </div> - <div class="image-fade"><span><?= /* @escapeNotVerified */ __('Hidden') ?></span></div> + <div class="image-fade"><span><?= $block->escapeHtml(__('Hidden')) ?></span></div> </div> <div class="item-description"> <div class="item-title" data-role="img-title"><%- data.label %></div> <div class="item-size"> - <span data-role="image-dimens"></span>, <span data-role="image-size"><%- data.sizeLabel %></span> + <span data-role="image-dimens"></span>, <span data-role="image-size"><%- data.sizeLabel %></span> </div> </div> @@ -106,12 +105,9 @@ $formName = $block->getFormName(); <?php foreach ($block->getImageTypes() as $typeData) { ?> - <li data-role-code="<?php /* @escapeNotVerified */ echo $block->escapeHtml( - $typeData['code'] - ) ?>" class="item-role item-role-<?php /* @escapeNotVerified */ echo $block->escapeHtml( - $typeData['code'] - ) ?>"> - <?= /* @escapeNotVerified */ $block->escapeHtml($typeData['label']) ?> + <li data-role-code="<?= $block->escapeHtmlAttr($typeData['code']) ?>" + class="item-role item-role-<?= $block->escapeHtmlAttr($typeData['code']) ?>"> + <?= $block->escapeHtml($typeData['label']) ?> </li> <?php } @@ -121,98 +117,94 @@ $formName = $block->getFormName(); </script> <script data-role="img-dialog-container-tmpl" type="text/x-magento-template"> - <div class="image-panel" data-role="dialog"> - </div> + <div class="image-panel" data-role="dialog"> + </div> </script> <script data-role="img-dialog-tmpl" type="text/x-magento-template"> - <div class="image-panel-preview"> - <img src="<%- data.url %>" alt="<%- data.label %>" /> - </div> - <div class="image-panel-controls"> - <strong class="image-name"><%- data.label %></strong> + <div class="image-panel-preview"> + <img src="<%- data.url %>" alt="<%- data.label %>" /> + </div> + <div class="image-panel-controls"> + <strong class="image-name"><%- data.label %></strong> - <fieldset class="admin__fieldset fieldset-image-panel"> - <div class="admin__field field-image-description"> - <label class="admin__field-label" for="image-description"> - <span><?= /* @escapeNotVerified */ __('Alt Text') ?></span> - </label> + <fieldset class="admin__fieldset fieldset-image-panel"> + <div class="admin__field field-image-description"> + <label class="admin__field-label" for="image-description"> + <span><?= $block->escapeHtml(__('Alt Text')) ?></span> + </label> - <div class="admin__field-control"> + <div class="admin__field-control"> <textarea data-role="image-description" rows="3" class="admin__control-textarea" - name="<?php /* @escapeNotVerified */ - echo $elementName - ?>[<%- data.file_id %>][label]"><%- data.label %></textarea> - </div> - </div> + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][label]"><%- data.label %></textarea> + </div> + </div> - <div class="admin__field field-image-role"> - <label class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('Role') ?></span> - </label> - <div class="admin__field-control"> - <ul class="multiselect-alt"> - <?php - foreach ($block->getMediaAttributes() as $attribute) : - ?> - <li class="item"> - <label> - <input class="image-type" - data-role="type-selector" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" - type="checkbox" - value="<?php /* @escapeNotVerified */ echo $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>" - /> - <?php /* @escapeNotVerified */ echo $block->escapeHtml( - $attribute->getFrontendLabel() - ) ?> - </label> - </li> + <div class="admin__field field-image-role"> + <label class="admin__field-label"> + <span><?= $block->escapeHtml(__('Role')) ?></span> + </label> + <div class="admin__field-control"> + <ul class="multiselect-alt"> + <?php + foreach ($block->getMediaAttributes() as $attribute) : + ?> + <li class="item"> + <label> + <input class="image-type" + data-role="type-selector" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" + type="checkbox" + value="<?= $block->escapeHtmlAttr($attribute->getAttributeCode()) ?>" + /> + <?= $block->escapeHtml( + $attribute->getFrontendLabel() + ) ?> + </label> + </li> <?php endforeach; - ?> - </ul> - </div> + ?> + </ul> </div> + </div> - <div class="admin__field admin__field-inline field-image-size" data-role="size"> - <label class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('Image Size') ?></span> - </label> - <div class="admin__field-value" data-message="<?= /* @escapeNotVerified */ __('{size}') ?>"></div> - </div> + <div class="admin__field admin__field-inline field-image-size" data-role="size"> + <label class="admin__field-label"> + <span><?= $block->escapeHtml(__('Image Size')) ?></span> + </label> + <div class="admin__field-value" data-message="<?= $block->escapeHtmlAttr(__('{size}')) ?>"></div> + </div> - <div class="admin__field admin__field-inline field-image-resolution" data-role="resolution"> - <label class="admin__field-label"> - <span><?= /* @escapeNotVerified */ __('Image Resolution') ?></span> - </label> - <div class="admin__field-value" data-message="<?= /* @escapeNotVerified */ __('{width}^{height} px') ?>"></div> - </div> + <div class="admin__field admin__field-inline field-image-resolution" data-role="resolution"> + <label class="admin__field-label"> + <span><?= $block->escapeHtml(__('Image Resolution')) ?></span> + </label> + <div class="admin__field-value" data-message="<?= $block->escapeHtmlAttr(__('{width}^{height} px')) ?>"></div> + </div> - <div class="admin__field field-image-hide"> - <div class="admin__field-control"> - <div class="admin__field admin__field-option"> - <input type="checkbox" - id="hide-from-product-page" - data-role="visibility-trigger" - data-form-part="<?= /* @escapeNotVerified */ $formName ?>" - value="1" - class="admin__control-checkbox" - name="<?= /* @escapeNotVerified */ $elementName ?>[<%- data.file_id %>][disabled]" - <% if (data.disabled == 1) { %>checked="checked"<% } %> /> - - <label for="hide-from-product-page" class="admin__field-label"> - <?= /* @escapeNotVerified */ __('Hide from Product Page') ?> - </label> - </div> + <div class="admin__field field-image-hide"> + <div class="admin__field-control"> + <div class="admin__field admin__field-option"> + <input type="checkbox" + id="hide-from-product-page" + data-role="visibility-trigger" + data-form-part="<?= $block->escapeHtmlAttr($formName) ?>" + value="1" + class="admin__control-checkbox" + name="<?= $block->escapeHtmlAttr($elementName) ?>[<%- data.file_id %>][disabled]" + <% if (data.disabled == 1) { %>checked="checked"<% } %> /> + + <label for="hide-from-product-page" class="admin__field-label"> + <?= $block->escapeHtml(__('Hide from Product Page')) ?> + </label> </div> </div> - </fieldset> - </div> + </div> + </fieldset> + </div> </script> <?= $block->getChildHtml('new-video') ?> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/js.phtml index 4134392c0f52b..0a13aee5930ad 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/js.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var \Magento\Catalog\Block\Adminhtml\Product\Edit\Js $block */ ?> @@ -30,8 +30,8 @@ function registerTaxRecalcs() { Event.observe($('tax_class_id'), 'change', recalculateTax); } -var priceFormat = <?= /* @escapeNotVerified */ $this->helper('Magento\Tax\Helper\Data')->getPriceFormat($block->getStore()) ?>; -var taxRates = <?= /* @escapeNotVerified */ $block->getAllRatesByProductClassJson() ?>; +var priceFormat = <?= /* @noEscape */ $this->helper(Magento\Tax\Helper\Data::class)->getPriceFormat($block->getStore()) ?>; +var taxRates = <?= /* @noEscape */ $block->getAllRatesByProductClassJson() ?>; function recalculateTax() { if (typeof dynamicTaxes == 'undefined') { @@ -75,10 +75,10 @@ function bindActiveProductTab(event, ui) { jQuery(document).on('tabsactivate', bindActiveProductTab); // bind active tab -<?php if ($tabsBlock = $block->getLayout()->getBlock('product_tabs')): ?> +<?php if ($tabsBlock = $block->getLayout()->getBlock('product_tabs')) :?> jQuery(function () { - if (jQuery('#<?= /* @escapeNotVerified */ $tabsBlock->getId() ?>').length && jQuery('#<?= /* @escapeNotVerified */ $tabsBlock->getId() ?>').is(':mage-tabs')) { - var activeAnchor = jQuery('#<?= /* @escapeNotVerified */ $tabsBlock->getId() ?>').tabs('activeAnchor'); + if (jQuery('#<?= $block->escapeJs($tabsBlock->getId()) ?>').length && jQuery('#<?= $block->escapeJs($tabsBlock->getId()) ?>').is(':mage-tabs')) { + var activeAnchor = jQuery('#<?= $block->escapeJs($tabsBlock->getId()) ?>').tabs('activeAnchor'); if (activeAnchor && $('store_switcher')) { $('store_switcher').switchParams = 'active_tab/' + activeAnchor.prop('name') + '/'; } diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/alert.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/alert.phtml index 5b07121de49dc..7c3bee3d4d2fc 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/alert.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/alert.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @@ -14,7 +12,7 @@ ?> <div id="alert_messages_block"><?= $block->getMessageHtml() ?></div> <div> - <h4 class="icon-head head-edit-form"><?= /* @escapeNotVerified */ __('Product Alerts') ?></h4> + <h4 class="icon-head head-edit-form"><?= $block->escapeHtml(__('Product Alerts')) ?></h4> </div> <div class="clear"></div> <?= $block->getAccordionHtml() ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/inventory.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/inventory.phtml index 2c62bbf8db3e9..5028d3c1e83d0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/inventory.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/tab/inventory.phtml @@ -4,382 +4,448 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Inventory */ ?> -<?php if ($block->isReadonly()): ?> -<?php $_readonly = ' disabled="disabled" '; ?> -<?php else: ?> -<?php $_readonly = ''; ?> +<?php if ($block->isReadonly()) :?> + <?php $_readonly = ' disabled="disabled" '; ?> +<?php else :?> + <?php $_readonly = ''; ?> <?php endif; ?> <fieldset class="fieldset form-inline"> -<legend class="legend"><span><?= /* @escapeNotVerified */ __('Advanced Inventory') ?></span></legend> -<br> -<div id="table_cataloginventory"> -<div class="field"> - <label class="label" for="inventory_manage_stock"> - <span><?= /* @escapeNotVerified */ __('Manage Stock') ?></span> - </label> - <div class="control"> - <select id="inventory_manage_stock" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][manage_stock]" <?= /* @escapeNotVerified */ $_readonly ?>> - <option value="1"><?= /* @escapeNotVerified */ __('Yes') ?></option> - <option value="0"<?php if ($block->getFieldValue('manage_stock') == 0): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('No') ?></option> - </select> - <input type="hidden" id="inventory_manage_stock_default" value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('manage_stock') ?>"> - <?php $_checked = ($block->getFieldValue('use_config_manage_stock') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_manage_stock" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_manage_stock]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_manage_stock"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_manage_stock'), $('inventory_use_config_manage_stock').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <legend class="legend"><span><?= $block->escapeHtml(__('Advanced Inventory')) ?></span></legend> + <br> + <div id="table_cataloginventory"> + <div class="field"> + <label class="label" for="inventory_manage_stock"> + <span><?= $block->escapeHtml(__('Manage Stock')) ?></span> + </label> + <div class="control"> + <select id="inventory_manage_stock" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][manage_stock]" <?= /* @noEscape */ $_readonly ?>> + <option value="1"><?= $block->escapeHtml(__('Yes')) ?></option> + <option value="0"<?php if ($block->getFieldValue('manage_stock') == 0) :?> selected="selected"<?php endif; ?>><?= $block->escapeHtml(__('No')) ?></option> + </select> + <input type="hidden" + id="inventory_manage_stock_default" + value="<?= $block->escapeHtmlAttr($block->getDefaultConfigValue('manage_stock')) ?>"> + <?php $_checked = ($block->getFieldValue('use_config_manage_stock') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_manage_stock" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_manage_stock]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_manage_stock"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_manage_stock'), $('inventory_use_config_manage_stock').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> -<?php if (!$block->getProduct()->isComposite()): ?> -<div class="field"> - <label class="label" for="inventory_qty"> - <span><?= /* @escapeNotVerified */ __('Qty') ?></span> - </label> - <div class="control"> - <?php if (!$_readonly): ?> - <input type="hidden" id="original_inventory_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][original_inventory_qty]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('qty') * 1 ?>"> - <?php endif;?> - <input type="text" class="input-text validate-number" id="inventory_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][qty]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('qty') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <?php if (!$block->getProduct()->isComposite()) :?> + <div class="field"> + <label class="label" for="inventory_qty"> + <span><?= $block->escapeHtml(__('Qty')) ?></span> + </label> + <div class="control"> + <?php if (!$_readonly) :?> + <input type="hidden" + id="original_inventory_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][original_inventory_qty]" + value="<?= $block->getFieldValue('qty') * 1 ?>"> + <?php endif;?> + <input type="text" + class="input-text validate-number" + id="inventory_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][qty]" + value="<?= $block->getFieldValue('qty') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> -<div class="field"> - <label class="label" for="inventory_min_qty"> - <span><?= /* @escapeNotVerified */ __('Out-of-Stock Threshold') ?></span> - </label> - <div class="control"> - <input type="text" class="input-text validate-number" id="inventory_min_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][min_qty]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('min_qty') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> + <div class="field"> + <label class="label" for="inventory_min_qty"> + <span><?= $block->escapeHtml(__('Out-of-Stock Threshold')) ?></span> + </label> + <div class="control"> + <input type="text" + class="input-text validate-number" + id="inventory_min_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][min_qty]" + value="<?= $block->getFieldValue('min_qty') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_min_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_min_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_min_qty]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_min_qty"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - </div> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_min_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_min_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_min_qty]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_min_qty"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(["prototype"], function(){ -toggleValueElements($('inventory_use_config_min_qty'), $('inventory_use_config_min_qty').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <?php if (!$block->isReadonly()) :?> + <script> + require(["prototype"], function(){ + toggleValueElements($('inventory_use_config_min_qty'), $('inventory_use_config_min_qty').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> -<div class="field"> - <label class="label" for="inventory_min_sale_qty"> - <span><?= /* @escapeNotVerified */ __('Minimum Qty Allowed in Shopping Cart') ?></span> - </label> - <div class="control"> - <input type="text" class="input-text validate-number" id="inventory_min_sale_qty" - name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][min_sale_qty]" - value="<?= /* @escapeNotVerified */ $block->getFieldValue('min_sale_qty') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_min_sale_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_min_sale_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_min_sale_qty]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" class="checkbox" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_min_sale_qty"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_min_sale_qty'), $('inventory_use_config_min_sale_qty').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <div class="field"> + <label class="label" for="inventory_min_sale_qty"> + <span><?= $block->escapeHtml(__('Minimum Qty Allowed in Shopping Cart')) ?></span> + </label> + <div class="control"> + <input type="text" class="input-text validate-number" id="inventory_min_sale_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][min_sale_qty]" + value="<?= $block->getFieldValue('min_sale_qty') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_min_sale_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_min_sale_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_min_sale_qty]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" + class="checkbox" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_min_sale_qty"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_min_sale_qty'), $('inventory_use_config_min_sale_qty').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> -<div class="field"> - <label class="label" for="inventory_max_sale_qty"> - <span><?= /* @escapeNotVerified */ __('Maximum Qty Allowed in Shopping Cart') ?></span> - </label> - <div class="control"> - <input type="text" class="input-text validate-number" id="inventory_max_sale_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][max_sale_qty]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('max_sale_qty') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> - <?php $_checked = ($block->getFieldValue('use_config_max_sale_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> - <div class="control-inner-wrap"> - <input type="checkbox" id="inventory_use_config_max_sale_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_max_sale_qty]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" class="checkbox" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_max_sale_qty"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_max_sale_qty'), $('inventory_use_config_max_sale_qty').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <div class="field"> + <label class="label" for="inventory_max_sale_qty"> + <span><?= $block->escapeHtml(__('Maximum Qty Allowed in Shopping Cart')) ?></span> + </label> + <div class="control"> + <input type="text" + class="input-text validate-number" + id="inventory_max_sale_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][max_sale_qty]" + value="<?= $block->getFieldValue('max_sale_qty') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> + <?php $_checked = ($block->getFieldValue('use_config_max_sale_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> + <div class="control-inner-wrap"> + <input type="checkbox" + id="inventory_use_config_max_sale_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_max_sale_qty]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" + class="checkbox" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_max_sale_qty"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_max_sale_qty'), $('inventory_use_config_max_sale_qty').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> - <?php if ($block->canUseQtyDecimals()): ?> - <div class="field"> - <label class="label" for="inventory_is_qty_decimal"> - <span><?= /* @escapeNotVerified */ __('Qty Uses Decimals') ?></span> - </label> - <div class="control"> - <select id="inventory_is_qty_decimal" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][is_qty_decimal]" <?= /* @escapeNotVerified */ $_readonly ?>> - <option value="0"><?= /* @escapeNotVerified */ __('No') ?></option> - <option value="1"<?php if ($block->getFieldValue('is_qty_decimal') == 1): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('Yes') ?></option> - </select> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> - </div> + <?php if ($block->canUseQtyDecimals()) :?> + <div class="field"> + <label class="label" for="inventory_is_qty_decimal"> + <span><?= $block->escapeHtml(__('Qty Uses Decimals')) ?></span> + </label> + <div class="control"> + <select id="inventory_is_qty_decimal" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][is_qty_decimal]" <?= /* @noEscape */ $_readonly ?>> + <option value="0"><?= $block->escapeHtml(__('No')) ?></option> + <option value="1"<?php if ($block->getFieldValue('is_qty_decimal') == 1) :?> selected="selected"<?php endif; ?>><?= $block->escapeHtml(__('Yes')) ?></option> + </select> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> - <?php if (!$block->isVirtual()) : ?> - <div class="field"> - <label class="label" for="inventory_is_decimal_divided"> - <span><?= /* @escapeNotVerified */ __('Allow Multiple Boxes for Shipping') ?></span> - </label> - <div class="control"> - <select id="inventory_is_decimal_divided" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][is_decimal_divided]" <?= /* @escapeNotVerified */ $_readonly ?>> - <option value="0"><?= /* @escapeNotVerified */ __('No') ?></option> - <option value="1"<?php if ($block->getFieldValue('is_decimal_divided') == 1): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('Yes') ?></option> - </select> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> + <?php if (!$block->isVirtual()) :?> + <div class="field"> + <label class="label" for="inventory_is_decimal_divided"> + <span><?= $block->escapeHtml(__('Allow Multiple Boxes for Shipping')) ?></span> + </label> + <div class="control"> + <select id="inventory_is_decimal_divided" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][is_decimal_divided]" <?= /* @noEscape */ $_readonly ?>> + <option value="0"><?= $block->escapeHtml(__('No')) ?></option> + <option value="1"<?php if ($block->getFieldValue('is_decimal_divided') == 1) :?> + selected="selected"<?php endif; ?>><?= $block->escapeHtml(__('Yes')) ?></option> + </select> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> + <?php endif; ?> <?php endif; ?> - </div> - <?php endif; ?> - <?php endif; ?> -<div class="field"> - <label class="label" for="inventory_backorders"> - <span><?= /* @escapeNotVerified */ __('Backorders') ?></span> - </label> - <div class="control"> - <select id="inventory_backorders" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][backorders]" <?= /* @escapeNotVerified */ $_readonly ?>> - <?php foreach ($block->getBackordersOption() as $option): ?> - <?php $_selected = ($option['value'] == $block->getFieldValue('backorders')) ? 'selected="selected"' : '' ?> - <option value="<?= /* @escapeNotVerified */ $option['value'] ?>" <?= /* @escapeNotVerified */ $_selected ?>><?= /* @escapeNotVerified */ $option['label'] ?></option> - <?php endforeach; ?> - </select> + <div class="field"> + <label class="label" for="inventory_backorders"> + <span><?= $block->escapeHtml(__('Backorders')) ?></span> + </label> + <div class="control"> + <select id="inventory_backorders" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][backorders]" <?= /* @noEscape */ $_readonly ?>> + <?php foreach ($block->getBackordersOption() as $option) :?> + <?php $_selected = ($option['value'] == $block->getFieldValue('backorders')) ? 'selected="selected"' : '' ?> + <option value="<?= $block->escapeHtmlAttr($option['value']) ?>" <?= /* @noEscape */ $_selected ?>><?= $block->escapeHtml($option['label']) ?></option> + <?php endforeach; ?> + </select> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_backorders') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_backorders" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_backorders]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_backorders"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_backorders'), $('inventory_use_config_backorders').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_backorders') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_backorders" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_backorders]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_backorders"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_backorders'), $('inventory_use_config_backorders').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> -<div class="field"> - <label class="label" for="inventory_notify_stock_qty"> - <span><?= /* @escapeNotVerified */ __('Notify for Quantity Below') ?></span> - </label> - <div class="control"> - <input type="text" class="input-text validate-number" id="inventory_notify_stock_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][notify_stock_qty]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('notify_stock_qty') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> + <div class="field"> + <label class="label" for="inventory_notify_stock_qty"> + <span><?= $block->escapeHtml(__('Notify for Quantity Below')) ?></span> + </label> + <div class="control"> + <input type="text" + class="input-text validate-number" + id="inventory_notify_stock_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][notify_stock_qty]" + value="<?= $block->getFieldValue('notify_stock_qty') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_notify_stock_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_notify_stock_qty" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_notify_stock_qty]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_notify_stock_qty"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> - </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_notify_stock_qty'), $('inventory_use_config_notify_stock_qty').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_notify_stock_qty') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_notify_stock_qty" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_notify_stock_qty]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_notify_stock_qty"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_notify_stock_qty'), $('inventory_use_config_notify_stock_qty').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> - <?php endif; ?> -<div class="field"> - <label class="label" for="inventory_enable_qty_increments"> - <span><?= /* @escapeNotVerified */ __('Enable Qty Increments') ?></span> - </label> - <div class="control"> - <?php $qtyIncrementsEnabled = $block->getFieldValue('enable_qty_increments'); ?> - <select id="inventory_enable_qty_increments" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][enable_qty_increments]" <?= /* @escapeNotVerified */ $_readonly ?>> - <option value="1"<?php if ($qtyIncrementsEnabled): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('Yes') ?></option> - <option value="0"<?php if (!$qtyIncrementsEnabled): ?> selected="selected"<?php endif; ?>><?= /* @escapeNotVerified */ __('No') ?></option> - </select> - <input type="hidden" id="inventory_enable_qty_increments_default" value="<?= /* @escapeNotVerified */ $block->getDefaultConfigValue('enable_qty_increments') ?>"> + <?php endif; ?> + <div class="field"> + <label class="label" for="inventory_enable_qty_increments"> + <span><?= $block->escapeHtml(__('Enable Qty Increments')) ?></span> + </label> + <div class="control"> + <?php $qtyIncrementsEnabled = $block->getFieldValue('enable_qty_increments'); ?> + <select id="inventory_enable_qty_increments" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][enable_qty_increments]" <?= /* @noEscape */ $_readonly ?>> + <option value="1"<?php if ($qtyIncrementsEnabled) :?> selected="selected"<?php endif; ?>><?= $block->escapeHtml(__('Yes')) ?></option> + <option value="0"<?php if (!$qtyIncrementsEnabled) :?> selected="selected"<?php endif; ?>><?= $block->escapeHtml(__('No')) ?></option> + </select> + <input type="hidden" + id="inventory_enable_qty_increments_default" + value="<?= $block->escapeHtmlAttr($block->getDefaultConfigValue('enable_qty_increments')) ?>"> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_enable_qty_inc') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_enable_qty_increments" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_enable_qty_increments]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_enable_qty_increments"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_enable_qty_inc') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_enable_qty_increments" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_enable_qty_increments]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_enable_qty_increments"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_enable_qty_increments'), $('inventory_use_config_enable_qty_increments').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_enable_qty_increments'), $('inventory_use_config_enable_qty_increments').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> -<div class="field"> - <label class="label" for="inventory_qty_increments"> - <span><?= /* @escapeNotVerified */ __('Qty Increments') ?></span> - </label> - <div class="control"> - <input type="text" class="input-text validate-digits" id="inventory_qty_increments" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][qty_increments]" value="<?= /* @escapeNotVerified */ $block->getFieldValue('qty_increments') * 1 ?>" <?= /* @escapeNotVerified */ $_readonly ?>> - <div class="control-inner-wrap"> - <?php $_checked = ($block->getFieldValue('use_config_qty_increments') || $block->isNew()) ? 'checked="checked"' : '' ?> - <input type="checkbox" id="inventory_use_config_qty_increments" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][use_config_qty_increments]" value="1" <?= /* @escapeNotVerified */ $_checked ?> onclick="toggleValueElements(this, this.parentNode);" <?= /* @escapeNotVerified */ $_readonly ?>> - <label for="inventory_use_config_qty_increments"><?= /* @escapeNotVerified */ __('Use Config Settings') ?></label> + <div class="field"> + <label class="label" for="inventory_qty_increments"> + <span><?= $block->escapeHtml(__('Qty Increments')) ?></span> + </label> + <div class="control"> + <input type="text" + class="input-text validate-digits" + id="inventory_qty_increments" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][qty_increments]" + value="<?= $block->getFieldValue('qty_increments') * 1 ?>" <?= /* @noEscape */ $_readonly ?>> + <div class="control-inner-wrap"> + <?php $_checked = ($block->getFieldValue('use_config_qty_increments') || $block->isNew()) ? 'checked="checked"' : '' ?> + <input type="checkbox" + id="inventory_use_config_qty_increments" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][use_config_qty_increments]" + value="1" <?= /* @noEscape */ $_checked ?> + onclick="toggleValueElements(this, this.parentNode);" <?= /* @noEscape */ $_readonly ?>> + <label for="inventory_use_config_qty_increments"><?= $block->escapeHtml(__('Use Config Settings')) ?></label> + </div> + <?php if (!$block->isReadonly()) :?> + <script> + require(['prototype'], function(){ + toggleValueElements($('inventory_use_config_qty_increments'), $('inventory_use_config_qty_increments').parentNode); + }); + </script> + <?php endif; ?> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> </div> - <?php if (!$block->isReadonly()): ?> - <script> -require(['prototype'], function(){ -toggleValueElements($('inventory_use_config_qty_increments'), $('inventory_use_config_qty_increments').parentNode); -}); -</script> - <?php endif; ?> - </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> -<div class="field"> - <label class="label" for="inventory_stock_availability"> - <span><?= /* @escapeNotVerified */ __('Stock Availability') ?></span> - </label> - <div class="control"> - <select id="inventory_stock_availability" name="<?= /* @escapeNotVerified */ $block->getFieldSuffix() ?>[stock_data][is_in_stock]" <?= /* @escapeNotVerified */ $_readonly ?>> - <?php foreach ($block->getStockOption() as $option): ?> - <?php $_selected = ($block->getFieldValue('is_in_stock') !== null && $option['value'] == $block->getFieldValue('is_in_stock')) ? 'selected="selected"' : '' ?> - <option value="<?= /* @escapeNotVerified */ $option['value'] ?>" <?= /* @escapeNotVerified */ $_selected ?>><?= /* @escapeNotVerified */ $option['label'] ?></option> - <?php endforeach; ?> - </select> + <div class="field"> + <label class="label" for="inventory_stock_availability"> + <span><?= $block->escapeHtml(__('Stock Availability')) ?></span> + </label> + <div class="control"> + <select id="inventory_stock_availability" + name="<?= /* @noEscape */ $block->getFieldSuffix() ?>[stock_data][is_in_stock]" <?= /* @noEscape */ $_readonly ?>> + <?php foreach ($block->getStockOption() as $option) :?> + <?php $_selected = ($block->getFieldValue('is_in_stock') !== null && $option['value'] == $block->getFieldValue('is_in_stock')) ? 'selected="selected"' : '' ?> + <option value="<?= $block->escapeHtmlAttr($option['value']) ?>" <?= /* @noEscape */ $_selected ?>><?= $block->escapeHtml($option['label']) ?></option> + <?php endforeach; ?> + </select> + </div> + <?php if (!$block->isSingleStoreMode()) :?> + <div class="field-service"><?= $block->escapeHtml(__('[GLOBAL]')) ?></div> + <?php endif; ?> + </div> </div> - <?php if (!$block->isSingleStoreMode()): ?> - <div class="field-service"><?= /* @escapeNotVerified */ __('[GLOBAL]') ?></div> - <?php endif; ?> -</div> -</div> </fieldset> <script> -require(["jquery","prototype"], function(jQuery){ + require(["jquery","prototype"], function(jQuery){ - //<![CDATA[ - function changeManageStockOption() - { - var manageStock = $('inventory_use_config_manage_stock').checked + //<![CDATA[ + function changeManageStockOption() + { + var manageStock = $('inventory_use_config_manage_stock').checked ? $('inventory_manage_stock_default').value : $('inventory_manage_stock').value; - var catalogInventoryNotManageStockFields = { - inventory_min_sale_qty: true, - inventory_max_sale_qty: true, - inventory_enable_qty_increments : true, - inventory_qty_increments: true - }; - - $$('#table_cataloginventory > div').each(function(el) { - if (el == $('inventory_manage_stock').up(1)) { - return; - } + var catalogInventoryNotManageStockFields = { + inventory_min_sale_qty: true, + inventory_max_sale_qty: true, + inventory_enable_qty_increments : true, + inventory_qty_increments: true + }; - for (field in catalogInventoryNotManageStockFields) { - if ($(field) && ($(field).up(1) == el)) { + $$('#table_cataloginventory > div').each(function(el) { + if (el == $('inventory_manage_stock').up(1)) { return; } - } - el[manageStock == 1 ? 'show' : 'hide'](); - }); + for (field in catalogInventoryNotManageStockFields) { + if ($(field) && ($(field).up(1) == el)) { + return; + } + } - return true; - } + el[manageStock == 1 ? 'show' : 'hide'](); + }); - function applyEnableQtyIncrements() { - var enableQtyIncrements = $('inventory_use_config_enable_qty_increments').checked - ? $('inventory_enable_qty_increments_default').value - : $('inventory_enable_qty_increments').value; + return true; + } - $('inventory_qty_increments').up('.field')[enableQtyIncrements == 1 ? 'show' : 'hide'](); - } + function applyEnableQtyIncrements() { + var enableQtyIncrements = $('inventory_use_config_enable_qty_increments').checked + ? $('inventory_enable_qty_increments_default').value + : $('inventory_enable_qty_increments').value; - function applyEnableDecimalDivided() { - <?php if (!$block->isVirtual()) : ?> - $('inventory_is_decimal_divided').up('.field').hide(); - <?php endif; ?> - $('inventory_qty_increments').removeClassName('validate-digits').removeClassName('validate-number'); - $('inventory_min_sale_qty').removeClassName('validate-digits').removeClassName('validate-number'); - if ($('inventory_is_qty_decimal').value == 1) { - <?php if (!$block->isVirtual()) : ?> - $('inventory_is_decimal_divided').up('.field').show(); - <?php endif; ?> - $('inventory_qty_increments').addClassName('validate-number'); - $('inventory_min_sale_qty').addClassName('validate-number'); - } else { - $('inventory_qty_increments').addClassName('validate-digits'); - $('inventory_min_sale_qty').addClassName('validate-digits'); + $('inventory_qty_increments').up('.field')[enableQtyIncrements == 1 ? 'show' : 'hide'](); } - } - Event.observe(window, 'load', function() { - if ($('inventory_manage_stock') && $('inventory_use_config_manage_stock')) { - Event.observe($('inventory_manage_stock'), 'change', changeManageStockOption); - Event.observe($('inventory_use_config_manage_stock'), 'change', changeManageStockOption); - changeManageStockOption(); + function applyEnableDecimalDivided() { + <?php if (!$block->isVirtual()) :?> + $('inventory_is_decimal_divided').up('.field').hide(); + <?php endif; ?> + $('inventory_qty_increments').removeClassName('validate-digits').removeClassName('validate-number'); + $('inventory_min_sale_qty').removeClassName('validate-digits').removeClassName('validate-number'); + if ($('inventory_is_qty_decimal').value == 1) { + <?php if (!$block->isVirtual()) :?> + $('inventory_is_decimal_divided').up('.field').show(); + <?php endif; ?> + $('inventory_qty_increments').addClassName('validate-number'); + $('inventory_min_sale_qty').addClassName('validate-number'); + } else { + $('inventory_qty_increments').addClassName('validate-digits'); + $('inventory_min_sale_qty').addClassName('validate-digits'); + } } - if ($('inventory_enable_qty_increments') && $('inventory_use_config_enable_qty_increments')) { - //Delegation is used because of events, which are not firing while the input is disabled - jQuery('#inventory_enable_qty_increments').parent() + + Event.observe(window, 'load', function() { + if ($('inventory_manage_stock') && $('inventory_use_config_manage_stock')) { + Event.observe($('inventory_manage_stock'), 'change', changeManageStockOption); + Event.observe($('inventory_use_config_manage_stock'), 'change', changeManageStockOption); + changeManageStockOption(); + } + if ($('inventory_enable_qty_increments') && $('inventory_use_config_enable_qty_increments')) { + //Delegation is used because of events, which are not firing while the input is disabled + jQuery('#inventory_enable_qty_increments').parent() .on('change', '#inventory_enable_qty_increments', applyEnableQtyIncrements); - Event.observe($('inventory_use_config_enable_qty_increments'), 'change', applyEnableQtyIncrements); - applyEnableQtyIncrements(); - } - if ($('inventory_is_qty_decimal') && $('inventory_qty_increments') && $('inventory_min_sale_qty')) { - Event.observe($('inventory_is_qty_decimal'), 'change', applyEnableDecimalDivided); - applyEnableDecimalDivided(); - } - }); + Event.observe($('inventory_use_config_enable_qty_increments'), 'change', applyEnableQtyIncrements); + applyEnableQtyIncrements(); + } + if ($('inventory_is_qty_decimal') && $('inventory_qty_increments') && $('inventory_min_sale_qty')) { + Event.observe($('inventory_is_qty_decimal'), 'change', applyEnableDecimalDivided); + applyEnableDecimalDivided(); + } + }); - window.applyEnableDecimalDivided = applyEnableDecimalDivided; - window.applyEnableQtyIncrements = applyEnableQtyIncrements; - window.changeManageStockOption = changeManageStockOption; - //]]> + window.applyEnableDecimalDivided = applyEnableDecimalDivided; + window.applyEnableQtyIncrements = applyEnableQtyIncrements; + window.changeManageStockOption = changeManageStockOption; + //]]> -}); + }); </script> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml index 04ccfb5aee8d0..17fb517b32547 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml @@ -4,24 +4,24 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Attributes\Search */ ?> <div id="product-attribute-search-container" class="suggest-expandable attribute-selector"> <div class="action-dropdown"> <button type="button" class="action-toggle action-choose" data-mage-init='{"dropdown":{}}' data-toggle="dropdown"> - <span><?= /* @escapeNotVerified */ __('Add Attribute') ?></span> + <span><?= $block->escapeHtml(__('Add Attribute')) ?></span> </button> <div class="dropdown-menu"> <input data-role="product-attribute-search" - data-group="<?= $block->escapeHtml($block->getGroupCode()) ?>" + data-group="<?= $block->escapeHtmlAttr($block->getGroupCode()) ?>" class="search" type="text" - placeholder="<?= /* @noEscape */ __('start typing to search attribute') ?>" /> + placeholder="<?= $block->escapeHtmlAttr(__('start typing to search attribute')) ?>" /> </div> </div> -<script data-template-for="product-attribute-search-<?= /* @escapeNotVerified */ $block->getGroupId() ?>" type="text/x-magento-template"> +<script data-template-for="product-attribute-search-<?= $block->escapeHtmlAttr($block->getGroupId()) ?>" type="text/x-magento-template"> <ul data-mage-init='{"menu":[]}'> <% if (data.items.length) { %> <% _.each(data.items, function(value){ %> @@ -29,7 +29,7 @@ <% }); %> <% } else { %><span class="mage-suggest-no-records"><%- data.noRecordsText %></span><% } %> </ul> - <div class="actions"><?= /* @escapeNotVerified */ $block->getAttributeCreate() ?></div> + <div class="actions"><?= $block->escapeHtml($block->getAttributeCreate()) ?></div> </script> <script> @@ -51,13 +51,13 @@ }); }); - $suggest.mage('suggest', <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getSelectorOptions()) ?>) + $suggest.mage('suggest', <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getSelectorOptions()) ?>) .on('suggestselect', function (e, ui) { $(this).val(''); var templateId = $('#attribute_set_id').val(); if (ui.item.id) { $.ajax({ - url: '<?= /* @escapeNotVerified */ $block->getAddAttributeUrl() ?>', + url: '<?= $block->escapeJs($block->escapeUrl($block->getAddAttributeUrl())) ?>', type: 'POST', dataType: 'json', data: {attribute_id: ui.item.id, template_id: templateId, group: $(this).data('group')}, diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml index 6a62f01f97b65..360694fceb241 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml @@ -4,40 +4,38 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs */ ?> -<?php if (!empty($tabs)): ?> +<?php if (!empty($tabs)) :?> <?php $tabGroups = [ \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE, \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::ADVANCED_TAB_GROUP_CODE, ];?> - <div id="<?= /* @escapeNotVerified */ $block->getId() ?>" + <div id="<?= $block->escapeHtmlAttr($block->getId()) ?>" data-mage-init='{"tabs":{ - "active": "<?= /* @escapeNotVerified */ $block->getActiveTabId() ?>", - "destination": "#<?= /* @escapeNotVerified */ $block->getDestElementId() ?>", - "shadowTabs": "<?= /* @escapeNotVerified */ $block->getAllShadowTabs() ?>", - "tabsBlockPrefix": "<?= /* @escapeNotVerified */ $block->getId() ?>_", + "active": "<?= $block->escapeHtmlAttr($block->getActiveTabId()) ?>", + "destination": "#<?= $block->escapeHtmlAttr($block->getDestElementId()) ?>", + "shadowTabs": "<?= /* @noEscape */ $block->getAllShadowTabs() ?>", + "tabsBlockPrefix": "<?= $block->escapeHtmlAttr($block->getId()) ?>_", "tabIdArgument": "active_tab", - "tabPanelClass": "<?= /* @escapeNotVerified */ $block->getPanelsClass() ?>", - "excludedPanel": "<?= /* @escapeNotVerified */ $block->getExcludedPanel() ?>", + "tabPanelClass": "<?= $block->escapeHtmlAttr($block->getPanelsClass()) ?>", + "excludedPanel": "<?= $block->escapeHtmlAttr($block->getExcludedPanel()) ?>", "groups": "ul.tabs" }}'> - <?php foreach ($tabGroups as $tabGroupCode): ?> + <?php foreach ($tabGroups as $tabGroupCode) :?> <?php $tabGroupId = $block->getId() . '-' . $tabGroupCode; $isBasic = $tabGroupCode == \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE; $activeCollapsible = $block->isAdvancedTabGroupActive() ? true : false; ?> - <div class="admin__page-nav <?php if (!$isBasic): ?> <?= '_collapsed' ?> <?php endif;?>" + <div class="admin__page-nav <?php if (!$isBasic) :?> <?= '_collapsed' ?> <?php endif;?>" data-role="container" - id="<?= /* @escapeNotVerified */ $tabGroupId ?>" - <?php if (!$isBasic): ?> + id="<?= $block->escapeHtmlAttr($tabGroupId) ?>" + <?php if (!$isBasic) :?> data-mage-init='{"collapsible":{ - "active": "<?= /* @escapeNotVerified */ $activeCollapsible ?>", + "active": "<?= /* @noEscape */ $activeCollapsible ?>", "openedState": "_show", "closedState": "_hide", "animate": 200, @@ -45,44 +43,45 @@ }}' <?php endif;?>> - <div class="admin__page-nav-title-wrap" <?= /* @escapeNotVerified */ $block->getUiId('title') ?> data-role="title"> - <div class="admin__page-nav-title <?php if (!$isBasic): ?> <?= '_collapsible' ?><?php endif;?>" + <div class="admin__page-nav-title-wrap" <?= /* @noEscape */ $block->getUiId('title') ?> data-role="title"> + <div class="admin__page-nav-title <?php if (!$isBasic) :?> <?= '_collapsible' ?><?php endif;?>" data-role="trigger"> <strong> - <?= /* @escapeNotVerified */ $isBasic ? __('Basic Settings') : __('Advanced Settings') ?> + <?= $block->escapeHtml($isBasic ? __('Basic Settings') : __('Advanced Settings')) ?> </strong> <span data-role="title-messages" class="admin__page-nav-title-messages"></span> </div> </div> - <ul <?= /* @escapeNotVerified */ $block->getUiId('tab', $tabGroupId) ?> class="tabs admin__page-nav-items" data-role="content"> - <?php foreach ($tabs as $_tab): ?> + <ul <?= /* @noEscape */ $block->getUiId('tab', $tabGroupId) ?> class="tabs admin__page-nav-items" data-role="content"> + <?php foreach ($tabs as $_tab) :?> <?php /** @var $_tab \Magento\Backend\Block\Widget\Tab\TabInterface */ ?> <?php if (!$block->canShowTab($_tab) || $_tab->getParentTab() || ($_tab->getGroupCode() && $_tab->getGroupCode() != $tabGroupCode) - || (!$_tab->getGroupCode() && $isBasic)): continue; endif;?> + || (!$_tab->getGroupCode() && $isBasic)) : continue; + endif;?> <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> - <li class="admin__page-nav-item <?php if ($block->getTabIsHidden($_tab)): ?> <?= "no-display" ?> <?php endif; ?> " <?= /* @escapeNotVerified */ $block->getUiId('tab', 'item', $_tab->getId()) ?>> - <a href="<?= /* @escapeNotVerified */ $_tabHref ?>" id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>" - name="<?= /* @escapeNotVerified */ $block->getTabId($_tab, false) ?>" - title="<?= /* @escapeNotVerified */ $block->getTabTitle($_tab) ?>" - class="admin__page-nav-link <?= /* @escapeNotVerified */ $_tabClass ?>" - data-tab-type="<?= /* @escapeNotVerified */ $_tabType ?>" <?= /* @escapeNotVerified */ $block->getUiId('tab', 'link', $_tab->getId()) ?> + <li class="admin__page-nav-item <?php if ($block->getTabIsHidden($_tab)) :?> <?= "no-display" ?> <?php endif; ?> " <?= /* @noEscape */ $block->getUiId('tab', 'item', $_tab->getId()) ?>> + <a href="<?= $block->escapeUrl($_tabHref) ?>" id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>" + name="<?= $block->escapeHtmlAttr($block->getTabId($_tab, false)) ?>" + title="<?= $block->escapeHtmlAttr($block->getTabTitle($_tab)) ?>" + class="admin__page-nav-link <?= $block->escapeHtmlAttr($_tabClass) ?>" + data-tab-type="<?= /* @noEscape */ $_tabType ?>" <?= /* @noEscape */ $block->getUiId('tab', 'link', $_tab->getId()) ?> > <span><?= $block->escapeHtml($block->getTabLabel($_tab)) ?></span> <span class="admin__page-nav-item-messages" data-role="item-messages"> <span class="admin__page-nav-item-message _changed"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip"> - <?= /* @escapeNotVerified */ __('Changes have been made to this section that have not been saved.') ?> + <?= $block->escapeHtml(__('Changes have been made to this section that have not been saved.')) ?> </span> </span> <span class="admin__page-nav-item-message _error"> <span class="admin__page-nav-item-message-icon"></span> <span class="admin__page-nav-item-message-tooltip"> - <?= /* @escapeNotVerified */ __('This tab contains invalid data. Please resolve this before saving.') ?> + <?= $block->escapeHtml(__('This tab contains invalid data. Please resolve this before saving.')) ?> </span> </span> <span class="admin__page-nav-item-message-loader"> @@ -93,11 +92,11 @@ </span> </span> </a> - <div id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>_content" class="no-display" - data-tab-panel="<?= /* @escapeNotVerified */ $_tab->getTabId() ?>" - <?= /* @escapeNotVerified */ $block->getUiId('tab', 'content', $_tab->getId()) ?> + <div id="<?= $block->escapeHtmlAttr($block->getTabId($_tab)) ?>_content" class="no-display" + data-tab-panel="<?= $block->escapeHtmlAttr($_tab->getTabId()) ?>" + <?= /* @noEscape */ $block->getUiId('tab', 'content', $_tab->getId()) ?> > - <?= /* @escapeNotVerified */ $block->getTabContent($_tab) ?> + <?= /* @noEscape */ $block->getTabContent($_tab) ?> <?= /* @noEscape */ $block->getAccordion($_tab) ?> </div> </li> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml index 842ed17375f77..c4dc1ddc0b02b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml @@ -4,13 +4,11 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\ChildTab */ ?> <div class="fieldset-wrapper admin__collapsible-block-wrapper" data-tab="<?= /* @noEscape */ $block->getTabId() ?>" id="<?= /* @noEscape */ $block->getTabId() ?>-wrapper" data-mage-init='{"collapsible":{ - "active": <?= /* @noEscape */ $block->isTabOpened() ? 'true' : 'false' ?>, + "active": <?= $block->isTabOpened() ? 'true' : 'false' ?>, "openedState": "_show", "closedState": "_hide", "animate": 200, diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/massaction_extended.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/massaction_extended.phtml index 8df3e32b0a2c3..c814298d1dbc5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/massaction_extended.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/massaction_extended.phtml @@ -4,11 +4,10 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Grid */ ?> <div id="<?= $block->getHtmlId() ?>" class="admin__grid-massaction"> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) :?> <form action="" id="<?= $block->getHtmlId() ?>-form" method="post"> <?php endif ?> <div class="admin__grid-massaction-form"> @@ -16,43 +15,43 @@ <select id="<?= $block->getHtmlId() ?>-select" class="local-validation admin__control-select"> - <option class="admin__control-select-placeholder" value="" selected><?= /* @escapeNotVerified */ __('Actions') ?></option> - <?php foreach ($block->getItems() as $_item): ?> - <option value="<?= /* @escapeNotVerified */ $_item->getId() ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= /* @escapeNotVerified */ $_item->getLabel() ?></option> + <option class="admin__control-select-placeholder" value="" selected><?= $block->escapeHtml(__('Actions')) ?></option> + <?php foreach ($block->getItems() as $_item) :?> + <option value="<?= $block->escapeHtmlAttr($_item->getId()) ?>"<?= ($_item->getSelected() ? ' selected="selected"' : '') ?>><?= $block->escapeHtml($_item->getLabel()) ?></option> <?php endforeach; ?> </select> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-hiddens"></span> <span class="outer-span" id="<?= $block->getHtmlId() ?>-form-additional"></span> <?= $block->getApplyButtonHtml() ?> </div> - <?php if ($block->getHideFormElement() !== true):?> + <?php if ($block->getHideFormElement() !== true) :?> </form> <?php endif ?> <div class="no-display"> - <?php foreach ($block->getItems() as $_item): ?> - <div id="<?= $block->getHtmlId() ?>-item-<?= /* @escapeNotVerified */ $_item->getId() ?>-block"> + <?php foreach ($block->getItems() as $_item) :?> + <div id="<?= $block->getHtmlId() ?>-item-<?= $block->escapeHtmlAttr($_item->getId()) ?>-block"> <?= $_item->getAdditionalActionBlockHtml() ?> </div> <?php endforeach; ?> </div> <div class="mass-select-wrap"> - <select id="<?= $block->getHtmlId() ?>-mass-select" data-menu="grid-mass-select"> - <optgroup label="<?= /* @escapeNotVerified */ __('Mass Actions') ?>"> + <select id="<?= $block->escapeHtmlAttr($block->getHtmlId()) ?>-mass-select" data-menu="grid-mass-select"> + <optgroup label="<?= $block->escapeHtmlAttr(__('Mass Actions')) ?>"> <option disabled selected></option> - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) :?> <option value="selectAll"> - <?= /* @escapeNotVerified */ __('Select All') ?> + <?= $block->escapeHtml(__('Select All')) ?> </option> <option value="unselectAll"> - <?= /* @escapeNotVerified */ __('Unselect All') ?> + <?= $block->escapeHtml(__('Unselect All')) ?> </option> <?php endif; ?> <option value="selectVisible"> - <?= /* @escapeNotVerified */ __('Select Visible') ?> + <?= $block->escapeHtml(__('Select Visible')) ?> </option> <option value="unselectVisible"> - <?= /* @escapeNotVerified */ __('Unselect Visible') ?> + <?= $block->escapeHtml(__('Unselect Visible')) ?> </option> </optgroup> </select> @@ -65,19 +64,19 @@ $('#<?= $block->getHtmlId() ?>-mass-select').change(function () { var massAction = $('option:selected', this).val(); switch (massAction) { - <?php if ($block->getUseSelectAll()):?> + <?php if ($block->getUseSelectAll()) :?> case 'selectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectAll(); break case 'unselectAll': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectAll(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectAll(); break <?php endif; ?> case 'selectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.selectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.selectVisible(); break case 'unselectVisible': - return <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.unselectVisible(); + return <?= $block->escapeJs($block->getJsObjectName()) ?>.unselectVisible(); break } this.blur(); @@ -85,8 +84,8 @@ }); - <?php if (!$block->getParentBlock()->canDisplayContainer()): ?> - <?= /* @escapeNotVerified */ $block->getJsObjectName() ?>.setGridIds('<?= /* @escapeNotVerified */ $block->getGridIdsJson() ?>'); + <?php if (!$block->getParentBlock()->canDisplayContainer()) :?> + <?= $block->escapeJs($block->getJsObjectName()) ?>.setGridIds('<?= /* @noEscape */ $block->getGridIdsJson() ?>'); <?php endif; ?> </script> </div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/rss/grid/link.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/rss/grid/link.phtml index fb450d19312fa..668dc4a28a6d9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/rss/grid/link.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/rss/grid/link.phtml @@ -4,10 +4,8 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Adminhtml\Rss\Grid\Link */ ?> -<?php if ($block->isRssAllowed() && $block->getLink()): ?> -<a href="<?= /* @escapeNotVerified */ $block->getLink() ?>" class="link-feed"><?= /* @escapeNotVerified */ $block->getLabel() ?></a> +<?php if ($block->isRssAllowed() && $block->getLink()) :?> +<a href="<?= $block->escapeUrl($block->getLink()) ?>" class="link-feed"><?= $block->escapeHtml($block->getLabel()) ?></a> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml index d689daef4bcab..d2d6f098125ce 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml @@ -192,13 +192,6 @@ <label translate="true">Websites</label> </settings> </column> - <column name="cost" class="Magento\Catalog\Ui\Component\Listing\Columns\Price" sortOrder="120"> - <settings> - <addField>true</addField> - <filter>textRange</filter> - <label translate="true">Cost</label> - </settings> - </column> <actionsColumn name="actions" class="Magento\Catalog\Ui\Component\Listing\Columns\ProductActions" sortOrder="200"> <settings> <indexField>entity_id</indexField> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js index 9c66ce577a2fd..b71549c314d68 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js @@ -37,8 +37,8 @@ define([ ajax: { url: options.url, type: 'POST', - success: $.proxy(function (node) { - return this._convertData(node[0]); + success: $.proxy(function (nodes) { + return this._convertDataNodes(nodes); }, this), /** @@ -77,6 +77,21 @@ define([ } }, + /** + * @param {Array} nodes + * @returns {Array} + * @private + */ + _convertDataNodes: function (nodes) { + var nodesData = []; + + nodes.forEach(function (node) { + nodesData.push(this._convertData(node)); + }, this); + + return nodesData; + }, + /** * @param {Object} node * @return {*} diff --git a/app/code/Magento/Catalog/view/base/templates/js/components.phtml b/app/code/Magento/Catalog/view/base/templates/js/components.phtml index bad5acc209b5f..5902a9f25cc4b 100644 --- a/app/code/Magento/Catalog/view/base/templates/js/components.phtml +++ b/app/code/Magento/Catalog/view/base/templates/js/components.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml() ?> diff --git a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml index 0f3b4f481a288..dbc064665d3fe 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml @@ -4,11 +4,10 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile use Magento\Catalog\Model\Product\Option; /** - * @var \Magento\Catalog\Block\Product\View\Options\View\Checkable $block + * @var $block \Magento\Catalog\Block\Product\View\Options\Type\Select\Checkable */ $option = $block->getOption(); if ($option) : ?> @@ -19,27 +18,21 @@ if ($option) : ?> $count = 1; ?> -<div class="options-list nested" id="options-<?php echo /* @noEscape */ -$option->getId() ?>-list"> - <?php if ($optionType === Option::OPTION_TYPE_RADIO && !$option->getIsRequire()): ?> +<div class="options-list nested" id="options-<?= $block->escapeHtmlAttr($option->getId()) ?>-list"> + <?php if ($optionType === Option::OPTION_TYPE_RADIO && !$option->getIsRequire()) :?> <div class="field choice admin__field admin__field-option"> <input type="radio" - id="options_<?php echo /* @noEscape */ - $option->getId() ?>" + id="options_<?= $block->escapeHtmlAttr($option->getId()) ?>" class="radio admin__control-radio product-custom-option" - name="options[<?php echo /* @noEscape */ - $option->getId() ?>]" - data-selector="options[<?php echo /* @noEscape */ - $option->getId() ?>]" - onclick="<?php echo $block->getSkipJsReloadPrice() ? '' : 'opConfig.reloadPrice()' ?>" + name="options[<?= $block->escapeHtmlAttr($option->getId()) ?>]" + data-selector="options[<?= $block->escapeHtmlAttr($option->getId()) ?>]" + onclick="<?= $block->getSkipJsReloadPrice() ? '' : 'opConfig.reloadPrice()' ?>" value="" checked="checked" /> - <label class="label admin__field-label" for="options_<?php echo /* @noEscape */ - $option->getId() ?>"> + <label class="label admin__field-label" for="options_<?= $block->escapeHtmlAttr($option->getId()) ?>"> <span> - <?php echo /* @noEscape */ - __('None') ?> + <?= $block->escapeHtml(__('None')) ?> </span> </label> </div> @@ -60,41 +53,29 @@ $option->getId() ?>-list"> } ?> - <div class="field choice admin__field admin__field-option <?php echo /* @noEscape */ - $option->getIsRequire() ? 'required': '' ?>"> - <input type="<?php echo /* @noEscape */ - $optionType ?>" - class="<?php /** @noinspection DisconnectedForeachInstructionInspection */ - echo /* @noEscape */ - $optionType === Option::OPTION_TYPE_RADIO ? - 'radio admin__control-radio' : - 'checkbox admin__control-checkbox' ?> <?php echo /* @noEscape */ - $option->getIsRequire() ? 'required': '' ?> + <div class="field choice admin__field admin__field-option <?= /* @noEscape */ $option->getIsRequire() ? 'required': '' ?>"> + <input type="<?= $block->escapeHtmlAttr($optionType) ?>" + class="<?= $optionType === Option::OPTION_TYPE_RADIO + ? 'radio admin__control-radio' + : 'checkbox admin__control-checkbox' ?> <?= $option->getIsRequire() + ? 'required': '' ?> product-custom-option - <?php echo $block->getSkipJsReloadPrice() ? '' : 'opConfig.reloadPrice()' ?>" - name="options[<?php echo $option->getId() ?>]<?php echo /* @noEscape */ - $arraySign ?>" - id="options_<?php echo /* @noEscape */ - $option->getId() . '_' . $count ?>" - value="<?php echo /* @noEscape */ - $value->getOptionTypeId() ?>" - <?php echo /* @noEscape */ - $checked ?> - data-selector="<?php echo /* @noEscape */ - $dataSelector ?>" - price="<?php echo /* @noEscape */ - $block->getCurrencyByStore($value) ?>" + <?= $block->getSkipJsReloadPrice() ? '' : 'opConfig.reloadPrice()' ?>" + name="options[<?= $block->escapeHtmlAttr($option->getId()) ?>]<?= /* @noEscape */ $arraySign ?>" + id="options_<?= $block->escapeHtmlAttr($option->getId() . '_' . $count) ?>" + value="<?= $block->escapeHtmlAttr($value->getOptionTypeId()) ?>" + <?= $block->escapeHtml($checked) ?> + data-selector="<?= $block->escapeHtmlAttr($dataSelector) ?>" + price="<?= $block->escapeHtmlAttr($block->getCurrencyByStore($value)) ?>" /> <label class="label admin__field-label" - for="options_<?php echo /* @noEscape */ - $option->getId() . '_' . $count ?>"> + for="options_<?= $block->escapeHtmlAttr($option->getId() . '_' . $count) ?>"> <span> - <?php echo $block->escapeHtml($value->getTitle()) ?> + <?= $block->escapeHtml($value->getTitle()) ?> </span> - <?php echo /* @noEscape */ - $block->formatPrice($value) ?> + <?= /* @noEscape */ $block->formatPrice($value) ?> </label> </div> <?php endforeach; ?> </div> -<?php endif; ?> \ No newline at end of file +<?php endif; ?> diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml index ce1561e382eed..b2c2acb7419bd 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml @@ -3,29 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php /** @var \Magento\Framework\Pricing\Render\Amount $block */ ?> +<?php /** @var $block \Magento\Framework\Pricing\Render\Amount */ ?> -<span class="price-container <?= /* @escapeNotVerified */ $block->getAdjustmentCssClasses() ?>" +<span class="price-container <?= $block->escapeHtmlAttr($block->getAdjustmentCssClasses()) ?>" <?= $block->getSchema() ? ' itemprop="offers" itemscope itemtype="http://schema.org/Offer"' : '' ?>> - <?php if ($block->getDisplayLabel()): ?> - <span class="price-label"><?= /* @escapeNotVerified */ $block->getDisplayLabel() ?></span> + <?php if ($block->getDisplayLabel()) :?> + <span class="price-label"><?= $block->escapeHtml($block->getDisplayLabel()) ?></span> <?php endif; ?> - <span <?php if ($block->getPriceId()): ?> id="<?= /* @escapeNotVerified */ $block->getPriceId() ?>"<?php endif;?> - <?= ($block->getPriceDisplayLabel()) ? 'data-label="' . $block->getPriceDisplayLabel() . $block->getPriceDisplayInclExclTaxes() . '"' : '' ?> - data-price-amount="<?= /* @escapeNotVerified */ $block->getDisplayValue() ?>" - data-price-type="<?= /* @escapeNotVerified */ $block->getPriceType() ?>" - class="price-wrapper <?= /* @escapeNotVerified */ $block->getPriceWrapperCss() ?>" - ><?= /* @escapeNotVerified */ $block->formatCurrency($block->getDisplayValue(), (bool)$block->getIncludeContainer()) ?></span> - <?php if ($block->hasAdjustmentsHtml()): ?> + <span <?php if ($block->getPriceId()) :?> id="<?= $block->escapeHtmlAttr($block->getPriceId()) ?>"<?php endif;?> + <?= ($block->getPriceDisplayLabel()) ? 'data-label="' . $block->escapeHtmlAttr($block->getPriceDisplayLabel() . $block->getPriceDisplayInclExclTaxes()) . '"' : '' ?> + data-price-amount="<?= $block->escapeHtmlAttr($block->getDisplayValue()) ?>" + data-price-type="<?= $block->escapeHtmlAttr($block->getPriceType()) ?>" + class="price-wrapper <?= $block->escapeHtmlAttr($block->getPriceWrapperCss()) ?>" + ><?= $block->escapeHtml($block->formatCurrency($block->getDisplayValue(), (bool)$block->getIncludeContainer()), ['span']) ?></span> + <?php if ($block->hasAdjustmentsHtml()) :?> <?= $block->getAdjustmentsHtml() ?> <?php endif; ?> - <?php if ($block->getSchema()): ?> - <meta itemprop="price" content="<?= /* @escapeNotVerified */ $block->getDisplayValue() ?>" /> - <meta itemprop="priceCurrency" content="<?= /* @escapeNotVerified */ $block->getDisplayCurrencyCode() ?>" /> + <?php if ($block->getSchema()) :?> + <meta itemprop="price" content="<?= $block->escapeHtmlAttr($block->getDisplayValue()) ?>" /> + <meta itemprop="priceCurrency" content="<?= $block->escapeHtmlAttr($block->getDisplayCurrencyCode()) ?>" /> <?php endif; ?> </span> diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/default.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/default.phtml index b414f02a3d6fb..7005e65bcca80 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/default.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/default.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -15,7 +12,7 @@ /** @var \Magento\Framework\Pricing\Price\PriceInterface $priceModel */ $priceModel = $block->getPriceType('regular_price'); -/* @escapeNotVerified */ echo $block->renderAmount($priceModel->getAmount(), [ +/* @noEscape */ echo $block->renderAmount($priceModel->getAmount(), [ 'price_id' => $block->getPriceId('product-price-'), 'include_container' => true ]); diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/final_price.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/final_price.phtml index 6e281bdef7afb..e56804a06de22 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/final_price.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -21,9 +18,9 @@ $finalPriceModel = $block->getPriceType('final_price'); $idSuffix = $block->getIdSuffix() ? $block->getIdSuffix() : ''; $schema = ($block->getZone() == 'item_view') ? true : false; ?> -<?php if ($block->hasSpecialPrice()): ?> +<?php if ($block->hasSpecialPrice()) :?> <span class="special-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($finalPriceModel->getAmount(), [ + <?= /* @noEscape */ $block->renderAmount($finalPriceModel->getAmount(), [ 'display_label' => __('Special Price'), 'price_id' => $block->getPriceId('product-price-' . $idSuffix), 'price_type' => 'finalPrice', @@ -32,7 +29,7 @@ $schema = ($block->getZone() == 'item_view') ? true : false; ]); ?> </span> <span class="old-price"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($priceModel->getAmount(), [ + <?= /* @noEscape */ $block->renderAmount($priceModel->getAmount(), [ 'display_label' => __('Regular Price'), 'price_id' => $block->getPriceId('old-price-' . $idSuffix), 'price_type' => 'oldPrice', @@ -40,8 +37,8 @@ $schema = ($block->getZone() == 'item_view') ? true : false; 'skip_adjustments' => true ]); ?> </span> -<?php else: ?> - <?php /* @escapeNotVerified */ echo $block->renderAmount($finalPriceModel->getAmount(), [ +<?php else :?> + <?= /* @noEscape */ $block->renderAmount($finalPriceModel->getAmount(), [ 'price_id' => $block->getPriceId('product-price-' . $idSuffix), 'price_type' => 'finalPrice', 'include_container' => true, @@ -49,14 +46,14 @@ $schema = ($block->getZone() == 'item_view') ? true : false; ]); ?> <?php endif; ?> -<?php if ($block->showMinimalPrice()): ?> - <?php if ($block->getUseLinkForAsLowAs()):?> - <a href="<?= /* @escapeNotVerified */ $block->getSaleableItem()->getProductUrl() ?>" class="minimal-price-link"> - <?= /* @escapeNotVerified */ $block->renderAmountMinimal() ?> +<?php if ($block->showMinimalPrice()) :?> + <?php if ($block->getUseLinkForAsLowAs()) :?> + <a href="<?= $block->escapeUrl($block->getSaleableItem()->getProductUrl()) ?>" class="minimal-price-link"> + <?= /* @noEscape */ $block->renderAmountMinimal() ?> </a> - <?php else:?> + <?php else :?> <span class="minimal-price-link"> - <?= /* @escapeNotVerified */ $block->renderAmountMinimal() ?> + <?= /* @noEscape */ $block->renderAmountMinimal() ?> </span> <?php endif?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/tier_prices.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/tier_prices.phtml index f5cffb99d75dd..5949b54268a62 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/tier_prices.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/tier_prices.phtml @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php +// phpcs:disable Magento2.Templates.ThisInTemplate +// phpcs:disable Generic.WhiteSpace.ScopeIndent + /** @var \Magento\Catalog\Pricing\Render\PriceBox $block */ /** @var \Magento\Catalog\Pricing\Price\TierPrice $tierPriceModel */ @@ -18,17 +18,17 @@ $msrpShowOnGesture = $block->getPriceType('msrp_price')->isShowPriceOnGesture(); $product = $block->getSaleableItem(); ?> <?php if (count($tierPrices)) : ?> - <ul class="<?= /* @escapeNotVerified */ ($block->hasListClass() ? $block->getListClass() : 'prices-tier items') ?>"> - <?php foreach ($tierPrices as $index => $price) : ?> - <li class="item"> - <?php + <ul class="<?= $block->escapeHtmlAttr(($block->hasListClass() ? $block->getListClass() : 'prices-tier items')) ?>"> + <?php foreach ($tierPrices as $index => $price) : ?> + <li class="item"> + <?php $productId = $product->getId(); $isSaleable = $product->isSaleable(); $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20); - if ($msrpShowOnGesture && $price['price']->getValue() < $product->getMsrp()): + if ($msrpShowOnGesture && $price['price']->getValue() < $product->getMsrp()) : $addToCartUrl = ''; if ($isSaleable) { - $addToCartUrl = $this->helper('\Magento\Checkout\Helper\Cart') + $addToCartUrl = $this->helper(\Magento\Checkout\Helper\Cart::class) ->getAddUrl($product, ['qty' => $price['price_qty']]); } $tierPriceData = [ @@ -54,13 +54,13 @@ $product = $block->getSaleableItem(); if ($block->getCanDisplayQty($product)) { $tierPriceData['qty'] = $price['price_qty']; } - ?> - <?= /* @escapeNotVerified */ __('Buy %1 for: ', $price['price_qty']) ?> - <a href="javascript:void(0);" - id="<?= /* @escapeNotVerified */ ($popupId) ?>" - data-tier-price="<?= $block->escapeHtml($block->jsonEncode($tierPriceData)) ?>"> - <?= /* @escapeNotVerified */ __('Click for price') ?></a> - <?php else: + ?> + <?= $block->escapeHtml(__('Buy %1 for: ', $price['price_qty'])) ?> + <a href="javascript:void(0);" + id="<?= $block->escapeHtmlAttr($popupId) ?>" + data-tier-price="<?= $block->escapeHtml($block->jsonEncode($tierPriceData)) ?>"> + <?= $block->escapeHtml(__('Click for price')) ?></a> + <?php else : $priceAmountBlock = $block->renderAmount( $price['price'], [ @@ -70,22 +70,22 @@ $product = $block->getSaleableItem(); 'zone' => \Magento\Framework\Pricing\Render::ZONE_ITEM_OPTION ] ); - ?> - <?php /* @escapeNotVerified */ echo ($block->getShowDetailedPrice() !== false) - ? __( - 'Buy %1 for %2 each and <strong class="benefit">save<span class="percent tier-%3"> %4</span>%</strong>', - $price['price_qty'], - $priceAmountBlock, - $index, - $tierPriceModel->getSavePercent($price['price']) - ) - : __('Buy %1 for %2 each', $price['price_qty'], $priceAmountBlock); - ?> - <?php endif; ?> - </li> - <?php endforeach; ?> + ?> + <?= /* @noEscape */ ($block->getShowDetailedPrice() !== false) + ? __( + 'Buy %1 for %2 each and <strong class="benefit">save<span class="percent tier-%3"> %4</span>%</strong>', + $price['price_qty'], + $priceAmountBlock, + $index, + $block->formatPercent($price['percentage_value'] ?? $tierPriceModel->getSavePercent($price['price'])) + ) + : __('Buy %1 for %2 each', $price['price_qty'], $priceAmountBlock); + ?> + <?php endif; ?> + </li> + <?php endforeach; ?> </ul> - <?php if ($msrpShowOnGesture):?> + <?php if ($msrpShowOnGesture) :?> <script type="text/x-magento-init"> { ".product-info-main": { @@ -95,9 +95,9 @@ $product = $block->getSaleableItem(); "inputQty": "#qty", "attr": "[data-tier-price]", "productForm": "#product_addtocart_form", - "productId": "<?= /* @escapeNotVerified */ $productId ?>", + "productId": "<?= (int) $productId ?>", "productIdInput": "input[type=hidden][name=product]", - "isSaleable": "<?= /* @escapeNotVerified */ $isSaleable ?>" + "isSaleable": "<?= (bool) $isSaleable ?>" } } } diff --git a/app/code/Magento/Catalog/view/base/web/js/price-box.js b/app/code/Magento/Catalog/view/base/web/js/price-box.js index 783d39cddbc76..02ae6eadef672 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-box.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-box.js @@ -11,7 +11,7 @@ define([ 'Magento_Catalog/js/price-utils', 'underscore', 'mage/template', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($, utils, _, mageTemplate) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/base/web/js/price-option-date.js b/app/code/Magento/Catalog/view/base/web/js/price-option-date.js index 4ebfed097e27e..6c6299ca9a1f3 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-option-date.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-option-date.js @@ -7,7 +7,7 @@ define([ 'jquery', 'priceUtils', 'priceOptions', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($, utils) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/base/web/js/price-option-file.js b/app/code/Magento/Catalog/view/base/web/js/price-option-file.js index fffc2910b3fa8..0be38707869e9 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-option-file.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-option-file.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/base/web/js/price-options.js b/app/code/Magento/Catalog/view/base/web/js/price-options.js index e18abe3af38a6..d916d466fe978 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-options.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-options.js @@ -9,7 +9,7 @@ define([ 'mage/template', 'priceUtils', 'priceBox', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($, _, mageTemplate, utils) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/cms.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/cms.phtml index 3619ce94031c2..b50095e91d999 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/cms.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/cms.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -14,7 +11,7 @@ * @var $block \Magento\Catalog\Block\Category\View */ ?> -<?php if ($block->isContentMode() || $block->isMixedMode()): ?> +<?php if ($block->isContentMode() || $block->isMixedMode()) :?> <div class="category-cms"> <?= $block->getCmsBlockHtml() ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/description.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/description.phtml index 0efce1014f9c2..2f5b852575c78 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/description.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/description.phtml @@ -3,19 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + /** * Category view template * * @var $block \Magento\Catalog\Block\Category\View */ ?> -<?php if ($_description = $block->getCurrentCategory()->getDescription()): ?> +<?php if ($_description = $block->getCurrentCategory()->getDescription()) :?> <div class="category-description"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Output')->categoryAttribute($block->getCurrentCategory(), $_description, 'description') ?> + <?= /* @noEscape */ $this->helper(Magento\Catalog\Helper\Output::class)->categoryAttribute( + $block->getCurrentCategory(), + $_description, + 'description' + ) ?> </div> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml index edff2147ad14b..02593d3b541a1 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,14 +10,24 @@ * * @var $block \Magento\Catalog\Block\Category\View */ + +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact +// phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput ?> <?php - $_helper = $this->helper('Magento\Catalog\Helper\Output'); + $_helper = $this->helper(Magento\Catalog\Helper\Output::class); $_category = $block->getCurrentCategory(); $_imgHtml = ''; if ($_imgUrl = $_category->getImageUrl()) { - $_imgHtml = '<div class="category-image"><img src="' . $_imgUrl . '" alt="' . $block->escapeHtml($_category->getName()) . '" title="' . $block->escapeHtml($_category->getName()) . '" class="image" /></div>'; + $_imgHtml = '<div class="category-image"><img src="' + . $block->escapeUrl($_imgUrl) + . '" alt="' + . $block->escapeHtmlAttr($_category->getName()) + . '" title="' + . $block->escapeHtmlAttr($_category->getName()) + . '" class="image" /></div>'; $_imgHtml = $_helper->categoryAttribute($_category, $_imgHtml, 'image'); - /* @escapeNotVerified */ echo $_imgHtml; + /* @noEscape */ echo $_imgHtml; } ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/products.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/products.phtml index c521cf03ad156..80a9ae0a03e66 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/products.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/products.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -14,6 +11,6 @@ * @var $block \Magento\Catalog\Block\Category\View */ ?> -<?php if (!$block->isContentMode() || $block->isMixedMode()): ?> +<?php if (!$block->isContentMode() || $block->isMixedMode()) :?> <?= $block->getProductListHtml() ?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/rss.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/rss.phtml index 774aa8d839e87..65ee7ea789e46 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/rss.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/rss.phtml @@ -4,9 +4,9 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Category\Rss\Link */ ?> -<?php if ($block->isRssAllowed() && $block->getLink() && $block->isTopCategory()): ?> - <a href="<?= /* @escapeNotVerified */ $block->getLink() ?>" class="action link rss"><span><?= /* @escapeNotVerified */ $block->getLabel() ?></span></a> +<?php if ($block->isRssAllowed() && $block->getLink() && $block->isTopCategory()) :?> + <a href="<?= $block->escapeUrl($block->getLink()) ?>" + class="action link rss"><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_block.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_block.phtml index 2b0098be6545b..15fdd30c2d93f 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_block.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_block.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile ?> <div class="widget block block-category-link"> - <a <?= /* @escapeNotVerified */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> + <a <?= /* @noEscape */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_href.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_href.phtml index 91ab70b03769f..18ffee4b5f701 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_href.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_href.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** @var Magento\Catalog\Block\Widget\Link $block */ ?> <?= $block->escapeHtml($block->getHref()) ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_inline.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_inline.phtml index f53c1c1ed90d7..8f3b2ae613731 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_inline.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/widget/link/link_inline.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile ?> <span class="widget block block-category-link-inline"> - <a <?= /* @escapeNotVerified */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> + <a <?= /* @noEscape */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> </span> diff --git a/app/code/Magento/Catalog/view/frontend/templates/frontend_storage_manager.phtml b/app/code/Magento/Catalog/view/frontend/templates/frontend_storage_manager.phtml index 4c103b40ba28c..52bec7858a919 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/frontend_storage_manager.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/frontend_storage_manager.phtml @@ -3,7 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + +/** @var $block Magento\Catalog\Block\FrontendStorageManager */ ?> <script type="text/x-magento-init"> { @@ -12,9 +13,8 @@ "components": { "storage-manager": { "component": "Magento_Catalog/js/storage-manager", - "appendTo": "<?= /* @escapeNotVerified */ $block->getParentComponentName() ?>", - "storagesConfiguration" : - <?= /* @escapeNotVerified */ $block->getConfigurationJson() ?> + "appendTo": "<?= $block->escapeJs($block->getParentComponentName()) ?>", + "storagesConfiguration" : <?= /* @noEscape */ $block->getConfigurationJson() ?> } } } diff --git a/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml b/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml index 5f44c42e17c57..f5dca566abfed 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml @@ -3,12 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + /** @var \Magento\Framework\View\Element\Template $block */ ?> -<?= $block->escapeHtml(__( - 'You added product %1 to the <a href="%2">comparison list</a>.', - $block->getData('product_name'), - $block->getData('compare_list_url')), +<?= $block->escapeHtml( + __( + 'You added product %1 to the <a href="%2">comparison list</a>.', + $block->getData('product_name'), + $block->getData('compare_list_url') + ), ['a'] ); diff --git a/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml b/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml index 01820361744e0..6d5ddb95ab178 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * Category left navigation * @@ -15,25 +13,29 @@ <?php if (!$block->getCategory()) { return; } ?> -<?php $_categories = $block->getCurrentChildCategories(); ?> +<?php $_categories = $block->getCurrentChildCategories() ;?> <?php $_count = is_array($_categories) ? count($_categories) : $_categories->count(); ?> -<?php if ($_count): ?> +<?php if ($_count) :?> <div class="block filter"> <div class="title"> - <strong><?= /* @escapeNotVerified */ __('Shop By') ?></strong> + <strong><?= $block->escapeHtml(__('Shop By')) ?></strong> </div> <div class="content"> - <strong class="subtitle"><?= /* @escapeNotVerified */ __('Shopping Options') ?></strong> + <strong class="subtitle"><?= $block->escapeHtml(__('Shopping Options')) ?></strong> <dl class="options" id="narrow-by-list2"> - <dt><?= /* @escapeNotVerified */ __('Category') ?></dt> + <dt><?= $block->escapeHtml(__('Category')) ?></dt> <dd> <ol class="items"> <?php /** @var \Magento\Catalog\Model\Category $_category */ ?> - <?php foreach ($_categories as $_category): ?> - <?php if ($_category->getIsActive()): ?> + <?php foreach ($_categories as $_category) :?> + <?php if ($_category->getIsActive()) :?> <li class="item"> - <a href="<?= /* @escapeNotVerified */ $block->getCategoryUrl($_category) ?>"<?php if ($block->isCategoryActive($_category)): ?> class="current"<?php endif; ?>><?= $block->escapeHtml($_category->getName()) ?></a> - <span class="count"><?= /* @escapeNotVerified */ $_category->getProductCount() ?></span> + <a href="<?= $block->escapeUrl($block->getCategoryUrl($_category)) ?>" + <?php if ($block->isCategoryActive($_category)) :?> + class="current" + <?php endif; ?> + ><?= $block->escapeHtml($_category->getName()) ?></a> + <span class="count"><?= $block->escapeHtml($_category->getProductCount()) ?></span> </li> <?php endif; ?> <?php endforeach ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/compare/link.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/compare/link.phtml index b8595aae9d993..05a5649135ef5 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/compare/link.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/compare/link.phtml @@ -4,16 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +/** @var $block Magento\Framework\View\Element\Template */ ?> <li class="item link compare" data-bind="scope: 'compareProducts'" data-role="compare-products-link"> - <a class="action compare no-display" title="<?= /* @escapeNotVerified */ __('Compare Products') ?>" + <a class="action compare no-display" title="<?= $block->escapeHtmlAttr(__('Compare Products')) ?>" data-bind="attr: {'href': compareProducts().listUrl}, css: {'no-display': !compareProducts().count}" > - <?= /* @escapeNotVerified */ __('Compare Products') ?> + <?= $block->escapeHtml(__('Compare Products')) ?> <span class="counter qty" data-bind="text: compareProducts().countCaption"></span> </a> </li> <script type="text/x-magento-init"> -{"[data-role=compare-products-link]": {"Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>}} +{"[data-role=compare-products-link]": {"Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?>}} </script> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml index 7daf049980362..55772388d44bf 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml @@ -4,14 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable PSR2.ControlStructures.SwitchDeclaration +// phpcs:disable Generic.WhiteSpace.ScopeIndent /* @var $block \Magento\Catalog\Block\Product\Compare\ListCompare */ ?> <?php $total = $block->getItems()->getSize() ?> -<?php if ($total): ?> - <a href="#" class="action print hidden-print" title="<?= /* @escapeNotVerified */ __('Print This Page') ?>"> - <span><?= /* @escapeNotVerified */ __('Print This Page') ?></span> +<?php if ($total) :?> + <a href="#" class="action print hidden-print" title="<?= $block->escapeHtmlAttr(__('Print This Page')) ?>"> + <span><?= $block->escapeHtml(__('Print This Page')) ?></span> </a> <div class="table-wrapper comparison"> <table class="data table table-comparison" id="product-comparison" @@ -21,19 +23,19 @@ "selectors":{ "productAddToCartSelector":"button.action.tocart"} }}'> - <caption class="table-caption"><?= /* @escapeNotVerified */ __('Compare Products') ?></caption> + <caption class="table-caption"><?= $block->escapeHtml(__('Compare Products')) ?></caption> <thead> <tr> <?php $index = 0 ?> - <?php foreach ($block->getItems() as $item): ?> - <?php if ($index++ == 0): ?> - <th scope="row" class="cell label remove"><span><?= /* @escapeNotVerified */ __('Remove Product') ?></span></th> + <?php foreach ($block->getItems() as $item) :?> + <?php if ($index++ == 0) :?> + <th scope="row" class="cell label remove"><span><?= $block->escapeHtml(__('Remove Product')) ?></span></th> <?php endif; ?> <td class="cell remove product hidden-print"> - <?php $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare');?> - <a href="#" data-post='<?= /* @escapeNotVerified */ $compareHelper->getPostDataRemove($item) ?>' - class="action delete" title="<?= /* @escapeNotVerified */ __('Remove Product') ?>"> - <span><?= /* @escapeNotVerified */ __('Remove Product') ?></span> + <?php $compareHelper = $this->helper(Magento\Catalog\Helper\Product\Compare::class);?> + <a href="#" data-post='<?= /* @noEscape */ $compareHelper->getPostDataRemove($item) ?>' + class="action delete" title="<?= $block->escapeHtmlAttr(__('Remove Product')) ?>"> + <span><?= $block->escapeHtml(__('Remove Product')) ?></span> </a> </td> <?php endforeach; ?> @@ -42,44 +44,54 @@ <tbody> <tr> <?php $index = 0; ?> - <?php $helper = $this->helper('Magento\Catalog\Helper\Output'); ?> + <?php $helper = $this->helper(Magento\Catalog\Helper\Output::class); ?> <?php /** @var $item \Magento\Catalog\Model\Product */ ?> - <?php foreach ($block->getItems() as $item): ?> - <?php if ($index++ == 0): ?> - <th scope="row" class="cell label product"><span><?= /* @escapeNotVerified */ __('Product') ?></span></th> + <?php foreach ($block->getItems() as $item) :?> + <?php if ($index++ == 0) :?> + <th scope="row" class="cell label product"> + <span><?= $block->escapeHtml(__('Product')) ?></span> + </th> <?php endif; ?> - <td data-th="<?= $block->escapeHtml(__('Product')) ?>" class="cell product info"> - <a class="product-item-photo" href="<?= /* @escapeNotVerified */ $block->getProductUrl($item) ?>" title="<?= /* @escapeNotVerified */ $block->stripTags($item->getName(), null, true) ?>"> + <td data-th="<?= $block->escapeHtmlAttr(__('Product')) ?>" class="cell product info"> + <a class="product-item-photo" + href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>" + title="<?= /* @noEscape */ $block->stripTags($item->getName(), null, true) ?>"> <?= $block->getImage($item, 'product_comparison_list')->toHtml() ?> </a> <strong class="product-item-name"> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl($item) ?>" title="<?= /* @escapeNotVerified */ $block->stripTags($item->getName(), null, true) ?>"> - <?= /* @escapeNotVerified */ $helper->productAttribute($item, $item->getName(), 'name') ?> + <a href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>" + title="<?= /* @noEscape */ $block->stripTags($item->getName(), null, true) ?>"> + <?= /* @noEscape */ $helper->productAttribute($item, $item->getName(), 'name') ?> </a> </strong> <?= $block->getReviewsSummaryHtml($item, 'short') ?> - <?= /* @escapeNotVerified */ $block->getProductPrice($item, '-compare-list-top') ?> + <?= /* @noEscape */ $block->getProductPrice($item, '-compare-list-top') ?> <div class="product-item-actions hidden-print"> <div class="actions-primary"> - <?php if ($item->isSaleable()): ?> - <form data-role="tocart-form" action="<?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Product\Compare')->getAddToCartUrl($item) ?>" method="post"> + <?php if ($item->isSaleable()) :?> + <form data-role="tocart-form" + action="<?= $block->escapeUrl($this->helper(Magento\Catalog\Helper\Product\Compare::class)->getAddToCartUrl($item)) ?>" + method="post"> <?= $block->getBlockHtml('formkey') ?> <button type="submit" class="action tocart primary"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> </form> - <?php else: ?> - <?php if ($item->getIsSalable()): ?> - <div class="stock available"><span><?= /* @escapeNotVerified */ __('In stock') ?></span></div> - <?php else: ?> - <div class="stock unavailable"><span><?= /* @escapeNotVerified */ __('Out of stock') ?></span></div> + <?php else :?> + <?php if ($item->getIsSalable()) :?> + <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div> + <?php else :?> + <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div> <?php endif; ?> <?php endif; ?> </div> - <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow()) : ?> + <?php if ($this->helper(Magento\Wishlist\Helper\Data::class)->isAllow()) :?> <div class="secondary-addto-links actions-secondary" data-role="add-to-links"> - <a href="#" data-post='<?= /* @escapeNotVerified */ $block->getAddToWishlistParams($item) ?>' class="action towishlist" data-action="add-to-wishlist"> - <span><?= /* @escapeNotVerified */ __('Add to Wish List') ?></span> + <a href="#" + data-post='<?= /* @noEscape */ $block->getAddToWishlistParams($item) ?>' + class="action towishlist" + data-action="add-to-wishlist"> + <span><?= $block->escapeHtml(__('Add to Wish List')) ?></span> </a> </div> <?php endif; ?> @@ -89,12 +101,12 @@ </tr> </tbody> <tbody> - <?php foreach ($block->getAttributes() as $attribute): ?> + <?php foreach ($block->getAttributes() as $attribute) :?> <?php $index = 0; ?> - <?php if ($block->hasAttributeValueForProducts($attribute)): ?> + <?php if ($block->hasAttributeValueForProducts($attribute)) :?> <tr> - <?php foreach ($block->getItems() as $item): ?> - <?php if ($index++ == 0): ?> + <?php foreach ($block->getItems() as $item) :?> + <?php if ($index++ == 0) :?> <th scope="row" class="cell label"> <span class="attribute label"> <?= $block->escapeHtml($attribute->getStoreLabel() ? $attribute->getStoreLabel() : __($attribute->getFrontendLabel())) ?> @@ -105,21 +117,21 @@ <div class="attribute value"> <?php switch ($attribute->getAttributeCode()) { case "price": ?> - <?php - /* @escapeNotVerified */ echo $block->getProductPrice( - $item, - '-compare-list-' . $attribute->getCode() - ) + <?= + /* @noEscape */ $block->getProductPrice( + $item, + '-compare-list-' . $attribute->getCode() + ) ?> <?php break; case "small_image": ?> <?php $block->getImage($item, 'product_small_image')->toHtml(); ?> <?php break; - default: ?> - <?php if (is_string($block->getProductAttributeValue($item, $attribute))): ?> - <?= /* @escapeNotVerified */ $helper->productAttribute($item, $block->getProductAttributeValue($item, $attribute), $attribute->getAttributeCode()) ?> + default :?> + <?php if (is_string($block->getProductAttributeValue($item, $attribute))) :?> + <?= /* @noEscape */ $helper->productAttribute($item, $block->getProductAttributeValue($item, $attribute), $attribute->getAttributeCode()) ?> <?php endif; ?> - <?php break; + <?php break; } ?> </div> </td> @@ -130,7 +142,7 @@ </tbody> </table> </div> - <?php if (!$block->isRedirectToCartEnabled()) : ?> + <?php if (!$block->isRedirectToCartEnabled()) :?> <script type="text/x-magento-init"> { "[data-role=tocart-form]": { @@ -139,6 +151,6 @@ } </script> <?php endif; ?> -<?php else: ?> - <div class="message info empty"><div><?= /* @escapeNotVerified */ __('You have no items to compare.') ?></div></div> +<?php else :?> + <div class="message info empty"><div><?= $block->escapeHtml(__('You have no items to compare.')) ?></div></div> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/compare/sidebar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/compare/sidebar.phtml index 8daa342454445..809ddc5c61701 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/compare/sidebar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/compare/sidebar.phtml @@ -4,12 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + /* @var $block \Magento\Framework\View\Element\Template */ ?> <div class="block block-compare" data-bind="scope: 'compareProducts'" data-role="compare-products-sidebar"> <div class="block-title"> - <strong id="block-compare-heading" role="heading" aria-level="2"><?= /* @escapeNotVerified */ __('Compare Products') ?></strong> + <strong id="block-compare-heading" role="heading" aria-level="2"><?= $block->escapeHtml(__('Compare Products')) ?></strong> <span class="counter qty no-display" data-bind="text: compareProducts().countCaption, css: {'no-display': !compareProducts().count}"></span> </div> <!-- ko if: compareProducts().count --> @@ -20,29 +21,32 @@ <strong class="product-item-name"> <a data-bind="attr: {href: product_url}, html: name" class="product-item-link"></a> </strong> - <a href="#" data-bind="attr: {'data-post': remove_url}" title="<?= /* @escapeNotVerified */ __('Remove This Item') ?>" class="action delete"> - <span><?= /* @escapeNotVerified */ __('Remove This Item') ?></span> + <a href="#" + data-bind="attr: {'data-post': remove_url}" + title="<?= $block->escapeHtmlAttr(__('Remove This Item')) ?>" + class="action delete"> + <span><?= $block->escapeHtml(__('Remove This Item')) ?></span> </a> </li> </ol> <div class="actions-toolbar"> <div class="primary"> - <a data-bind="attr: {'href': compareProducts().listUrl}" class="action compare primary"><span><?= /* @escapeNotVerified */ __('Compare') ?></span></a> + <a data-bind="attr: {'href': compareProducts().listUrl}" class="action compare primary"><span><?= $block->escapeHtml(__('Compare')) ?></span></a> </div> <div class="secondary"> <a id="compare-clear-all" href="#" class="action clear" data-post="<?=$block->escapeHtml( - $this->helper('Magento\Catalog\Helper\Product\Compare')->getPostDataClearList() + $this->helper(Magento\Catalog\Helper\Product\Compare::class)->getPostDataClearList() ) ?>"> - <span><?= /* @escapeNotVerified */ __('Clear All') ?></span> + <span><?= $block->escapeHtml(__('Clear All')) ?></span> </a> </div> </div> </div> <!-- /ko --> <!-- ko ifnot: compareProducts().count --> - <div class="empty"><?= /* @escapeNotVerified */ __('You have no items to compare.') ?></div> + <div class="empty"><?= $block->escapeHtml(__('You have no items to compare.')) ?></div> <!-- /ko --> </div> <script type="text/x-magento-init"> -{"[data-role=compare-products-sidebar]": {"Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>}} +{"[data-role=compare-products-sidebar]": {"Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?>}} </script> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml index c7abb0525b302..e0443d5a55d97 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml @@ -4,39 +4,45 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Catalog\Block\Product\Gallery $block */ ?> <?php $_width = $block->getImageWidth(); ?> -<div class="product-image-popup" style="width:<?= /* @escapeNotVerified */ $_width ?>px;"> - <div class="buttons-set"><a href="#" class="button" role="close-window"><span><?= /* @escapeNotVerified */ __('Close Window') ?></span></a></div> - <?php if ($block->getPreviousImageUrl() || $block->getNextImageUrl()): ?> +<div class="product-image-popup" style="width:<?= /* @noEscape */ $_width ?>px;"> + <div class="buttons-set"><a href="#" class="button" role="close-window"><span><?= $block->escapeHtml(__('Close Window')) ?></span></a></div> + <?php if ($block->getPreviousImageUrl() || $block->getNextImageUrl()) :?> <div class="nav"> - <?php if ($_prevUrl = $block->getPreviousImageUrl()): ?> - <a href="<?= /* @escapeNotVerified */ $_prevUrl ?>" class="prev">« <?= /* @escapeNotVerified */ __('Prev') ?></a> - <?php endif; ?> - <?php if ($_nextUrl = $block->getNextImageUrl()): ?> - <a href="<?= /* @escapeNotVerified */ $_nextUrl ?>" class="next"><?= /* @escapeNotVerified */ __('Next') ?> »</a> - <?php endif; ?> + <?php if ($_prevUrl = $block->getPreviousImageUrl()) :?> + <a href="<?= $block->escapeUrl($_prevUrl) ?>" class="prev">« <?= $block->escapeHtml(__('Prev')) ?></a> + <?php endif; ?> + <?php if ($_nextUrl = $block->getNextImageUrl()) :?> + <a href="<?= $block->escapeUrl($_nextUrl) ?>" class="next"><?= $block->escapeHtml(__('Next')) ?> »</a> + <?php endif; ?> </div> <?php endif; ?> - <?php if ($_imageTitle = $block->escapeHtml($block->getCurrentImage()->getLabel())): ?> - <h1 class="image-label"><?= /* @escapeNotVerified */ $_imageTitle ?></h1> + <?php if ($_imageTitle = $block->escapeHtml($block->getCurrentImage()->getLabel())) :?> + <h1 class="image-label"><?= /* @noEscape */ $_imageTitle ?></h1> <?php endif; ?> <?php - $imageUrl = $block->getImageUrl(); + $imageUrl = $block->getImageUrl(); ?> - <img src="<?= /* @escapeNotVerified */ $imageUrl ?>"<?php if ($_width): ?> width="<?= /* @escapeNotVerified */ $_width ?>"<?php endif; ?> alt="<?= $block->escapeHtml($block->getCurrentImage()->getLabel()) ?>" title="<?= $block->escapeHtml($block->getCurrentImage()->getLabel()) ?>" id="product-gallery-image" class="image" data-mage-init='{"catalogGallery":{}}'/> - <div class="buttons-set"><a href="#" class="button" role="close-window"><span><?= /* @escapeNotVerified */ __('Close Window') ?></span></a></div> - <?php if ($block->getPreviousImageUrl() || $block->getNextImageUrl()): ?> + <img src="<?= $block->escapeUrl($imageUrl) ?>" + <?php if ($_width) :?> + width="<?= /* @noEscape */ $_width ?>" + <?php endif; ?> + alt="<?= $block->escapeHtmlAttr($block->getCurrentImage()->getLabel()) ?>" + title="<?= $block->escapeHtmlAttr($block->getCurrentImage()->getLabel()) ?>" + id="product-gallery-image" + class="image" + data-mage-init='{"catalogGallery":{}}'/> + <div class="buttons-set"><a href="#" class="button" role="close-window"><span><?= $block->escapeHtml(__('Close Window')) ?></span></a></div> + <?php if ($block->getPreviousImageUrl() || $block->getNextImageUrl()) :?> <div class="nav"> - <?php if ($_prevUrl = $block->getPreviousImageUrl()): ?> - <a href="<?= /* @escapeNotVerified */ $_prevUrl ?>" class="prev">« <?= /* @escapeNotVerified */ __('Prev') ?></a> - <?php endif; ?> - <?php if ($_nextUrl = $block->getNextImageUrl()): ?> - <a href="<?= /* @escapeNotVerified */ $_nextUrl ?>" class="next"><?= /* @escapeNotVerified */ __('Next') ?> »</a> - <?php endif; ?> + <?php if ($_prevUrl = $block->getPreviousImageUrl()) :?> + <a href="<?= $block->escapeUrl($_prevUrl) ?>" class="prev">« <?= $block->escapeHtml(__('Prev')) ?></a> + <?php endif; ?> + <?php if ($_nextUrl = $block->getNextImageUrl()) :?> + <a href="<?= $block->escapeUrl($_nextUrl) ?>" class="next"><?= $block->escapeHtml(__('Next')) ?> »</a> + <?php endif; ?> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index 94b829eb92137..5a31f3d125c81 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -6,9 +6,9 @@ ?> <?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?> -<img class="photo image" - <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> - src="<?= /* @escapeNotVerified */ $block->getImageUrl() ?>" - width="<?= /* @escapeNotVerified */ $block->getWidth() ?>" - height="<?= /* @escapeNotVerified */ $block->getHeight() ?>" - alt="<?= /* @escapeNotVerified */ $block->stripTags($block->getLabel(), null, true) ?>" /> +<img class="photo image <?= $block->escapeHtmlAttr($block->getClass()) ?>" + <?= $block->escapeHtml($block->getCustomAttributes()) ?> + src="<?= $block->escapeUrl($block->getImageUrl()) ?>" + width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" + height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" + alt="<?= /* @noEscape */ $block->stripTags($block->getLabel(), null, true) ?>" /> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 8a907bd54aa6a..33f7620f1a1f5 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -7,13 +7,13 @@ <?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?> <span class="product-image-container" - style="width:<?= /* @escapeNotVerified */ $block->getWidth() ?>px;"> + style="width:<?= $block->escapeHtmlAttr($block->getWidth()) ?>px;"> <span class="product-image-wrapper" - style="padding-bottom: <?= /* @escapeNotVerified */ ($block->getRatio() * 100) ?>%;"> - <img class="<?= /* @escapeNotVerified */ $block->getClass() ?>" - <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> - src="<?= /* @escapeNotVerified */ $block->getImageUrl() ?>" - max-width="<?= /* @escapeNotVerified */ $block->getWidth() ?>" - max-height="<?= /* @escapeNotVerified */ $block->getHeight() ?>" - alt="<?= /* @escapeNotVerified */ $block->stripTags($block->getLabel(), null, true) ?>"/></span> + style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> + <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>" + <?= $block->escapeHtmlAttr($block->getCustomAttributes()) ?> + src="<?= $block->escapeUrl($block->getImageUrl()) ?>" + max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" + max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" + alt="<?= /* @noEscape */ $block->stripTags($block->getLabel(), null, true) ?>"/></span> </span> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml index e970ade6cee96..ce44884a575b8 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml @@ -5,10 +5,10 @@ */ use Magento\Framework\App\Action\Action; -// @codingStandardsIgnoreFile - ?> <?php +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + /** * Product list template * @@ -17,11 +17,11 @@ use Magento\Framework\App\Action\Action; ?> <?php $_productCollection = $block->getLoadedProductCollection(); -$_helper = $this->helper('Magento\Catalog\Helper\Output'); +$_helper = $this->helper(Magento\Catalog\Helper\Output::class); ?> -<?php if (!$_productCollection->count()): ?> - <div class="message info empty"><div><?= /* @escapeNotVerified */ __('We can\'t find products matching the selection.') ?></div></div> -<?php else: ?> +<?php if (!$_productCollection->count()) :?> + <div class="message info empty"><div><?= $block->escapeHtml(__('We can\'t find products matching the selection.')) ?></div></div> +<?php else :?> <?= $block->getToolbarHtml() ?> <?= $block->getAdditionalHtml() ?> <?php @@ -41,12 +41,12 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); */ $pos = $block->getPositioned(); ?> - <div class="products wrapper <?= /* @escapeNotVerified */ $viewMode ?> products-<?= /* @escapeNotVerified */ $viewMode ?>"> + <div class="products wrapper <?= /* @noEscape */ $viewMode ?> products-<?= /* @noEscape */ $viewMode ?>"> <ol class="products list items product-items"> <?php /** @var $_product \Magento\Catalog\Model\Product */ ?> - <?php foreach ($_productCollection as $_product): ?> + <?php foreach ($_productCollection as $_product) :?> <li class="item product product-item"> - <div class="product-item-info" data-container="product-<?= /* @escapeNotVerified */ $viewMode ?>"> + <div class="product-item-info" data-container="product-<?= /* @noEscape */ $viewMode ?>"> <?php $productImage = $block->getImage($_product, $imageDisplayArea); if ($pos != null) { @@ -55,7 +55,9 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); } ?> <?php // Product Image ?> - <a href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" class="product photo product-item-photo" tabindex="-1"> + <a href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + class="product photo product-item-photo" + tabindex="-1"> <?= $productImage->toHtml() ?> </a> <div class="product details product-item-details"> @@ -64,48 +66,55 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); ?> <strong class="product name product-item-name"> <a class="product-item-link" - href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>"> - <?= /* @escapeNotVerified */ $_helper->productAttribute($_product, $_product->getName(), 'name') ?> + href="<?= $block->escapeUrl($_product->getProductUrl()) ?>"> + <?= /* @noEscape */ $_helper->productAttribute($_product, $_product->getName(), 'name') ?> </a> </strong> <?= $block->getReviewsSummaryHtml($_product, $templateType) ?> - <?= /* @escapeNotVerified */ $block->getProductPrice($_product) ?> + <?= /* @noEscape */ $block->getProductPrice($_product) ?> <?= $block->getProductDetailsHtml($_product) ?> <div class="product-item-inner"> - <div class="product actions product-item-actions"<?= strpos($pos, $viewMode . '-actions') ? $position : '' ?>> - <div class="actions-primary"<?= strpos($pos, $viewMode . '-primary') ? $position : '' ?>> - <?php if ($_product->isSaleable()): ?> + <div class="product actions product-item-actions"<?= strpos($pos, $viewMode . '-actions') ? $block->escapeHtmlAttr($position) : '' ?>> + <div class="actions-primary"<?= strpos($pos, $viewMode . '-primary') ? $block->escapeHtmlAttr($position) : '' ?>> + <?php if ($_product->isSaleable()) :?> <?php $postParams = $block->getAddToCartPostParams($_product); ?> - <form data-role="tocart-form" data-product-sku="<?= $block->escapeHtml($_product->getSku()) ?>" action="<?= /* @NoEscape */ $postParams['action'] ?>" method="post"> - <input type="hidden" name="product" value="<?= /* @escapeNotVerified */ $postParams['data']['product'] ?>"> - <input type="hidden" name="<?= /* @escapeNotVerified */ Action::PARAM_NAME_URL_ENCODED ?>" value="<?= /* @escapeNotVerified */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED] ?>"> + <form data-role="tocart-form" + data-product-sku="<?= $block->escapeHtml($_product->getSku()) ?>" + action="<?= $block->escapeUrl($postParams['action']) ?>" + method="post"> + <input type="hidden" + name="product" + value="<?= /* @noEscape */ $postParams['data']['product'] ?>"> + <input type="hidden" name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>" + value="<?= /* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED] ?>"> <?= $block->getBlockHtml('formkey') ?> <button type="submit" - title="<?= $block->escapeHtml(__('Add to Cart')) ?>" + title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>" class="action tocart primary"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> </form> - <?php else: ?> - <?php if ($_product->isAvailable()): ?> - <div class="stock available"><span><?= /* @escapeNotVerified */ __('In stock') ?></span></div> - <?php else: ?> - <div class="stock unavailable"><span><?= /* @escapeNotVerified */ __('Out of stock') ?></span></div> + <?php else :?> + <?php if ($_product->isAvailable()) :?> + <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div> + <?php else :?> + <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div> <?php endif; ?> <?php endif; ?> </div> - <div data-role="add-to-links" class="actions-secondary"<?= strpos($pos, $viewMode . '-secondary') ? $position : '' ?>> - <?php if ($addToBlock = $block->getChildBlock('addto')): ?> + <div data-role="add-to-links" class="actions-secondary"<?= strpos($pos, $viewMode . '-secondary') ? $block->escapeHtmlAttr($position) : '' ?>> + <?php if ($addToBlock = $block->getChildBlock('addto')) :?> <?= $addToBlock->setProduct($_product)->getChildHtml() ?> <?php endif; ?> </div> </div> - <?php if ($showDescription):?> + <?php if ($showDescription) :?> <div class="product description product-item-description"> - <?= /* @escapeNotVerified */ $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description') ?> - <a href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" title="<?= /* @escapeNotVerified */ $_productNameStripped ?>" - class="action more"><?= /* @escapeNotVerified */ __('Learn More') ?></a> + <?= /* @noEscape */ $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description') ?> + <a href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + title="<?= /* @noEscape */ $_productNameStripped ?>" + class="action more"><?= $block->escapeHtml(__('Learn More')) ?></a> </div> <?php endif; ?> </div> @@ -116,12 +125,12 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); </ol> </div> <?= $block->getToolbarHtml() ?> - <?php if (!$block->isRedirectToCartEnabled()) : ?> + <?php if (!$block->isRedirectToCartEnabled()) :?> <script type="text/x-magento-init"> { "[data-role=tocart-form], .form.map.checkout": { "catalogAddToCart": { - "product_sku": "<?= /* @NoEscape */ $_product->getSku() ?>" + "product_sku": "<?= $block->escapeJs($_product->getSku()) ?>" } } } diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/addto/compare.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/addto/compare.phtml index 8798170e8c0b0..c23ee021ca3a8 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/addto/compare.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/addto/compare.phtml @@ -4,14 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** @var $block Magento\Catalog\Block\Product\ProductList\Item\AddTo\Compare */ ?> <a href="#" class="action tocompare" title="<?= $block->escapeHtml(__('Add to Compare')) ?>" aria-label="<?= $block->escapeHtml(__('Add to Compare')) ?>" - data-post='<?= /* @escapeNotVerified */ $block->getCompareHelper()->getPostDataParams($block->getProduct()) ?>' + data-post='<?= /* @noEscape */ $block->getCompareHelper()->getPostDataParams($block->getProduct()) ?>' role="button"> - <span><?= /* @escapeNotVerified */ __('Add to Compare') ?></span> + <span><?= $block->escapeHtml(__('Add to Compare')) ?></span> </a> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml index ecc9700802d27..91e261900aef2 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml @@ -4,7 +4,8 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable Generic.WhiteSpace.ScopeIndent.Incorrect /* @var $block \Magento\Catalog\Block\Product\AbstractProduct */ ?> @@ -29,7 +30,7 @@ switch ($type = $block->getType()) { $templateType = null; $description = false; } - break; + break; case 'related': /** @var \Magento\Catalog\Block\Product\ProductList\Related $block */ @@ -49,7 +50,7 @@ switch ($type = $block->getType()) { $templateType = null; $description = false; } - break; + break; case 'upsell-rule': if ($exist = $block->hasItems()) { @@ -68,7 +69,7 @@ switch ($type = $block->getType()) { $description = false; $canItemsAddToCart = false; } - break; + break; case 'upsell': /** @var \Magento\Catalog\Block\Product\ProductList\Upsell $block */ @@ -88,7 +89,7 @@ switch ($type = $block->getType()) { $description = false; $canItemsAddToCart = false; } - break; + break; case 'crosssell-rule': /** @var \Magento\Catalog\Block\Product\ProductList\Crosssell $block */ @@ -106,7 +107,7 @@ switch ($type = $block->getType()) { $description = false; $canItemsAddToCart = false; } - break; + break; case 'crosssell': /** @var \Magento\Catalog\Block\Product\ProductList\Crosssell $block */ @@ -124,7 +125,7 @@ switch ($type = $block->getType()) { $description = false; $canItemsAddToCart = false; } - break; + break; case 'new': if ($exist = $block->getProductCollection()) { @@ -144,117 +145,117 @@ switch ($type = $block->getType()) { $description = ($mode == 'list') ? true : false; $canItemsAddToCart = false; } - break; + break; default: $exist = null; } ?> -<?php if ($exist):?> +<?php if ($exist) :?> - <?php if ($type == 'related' || $type == 'upsell'): ?> - <?php if ($type == 'related'): ?> - <div class="block <?= /* @escapeNotVerified */ $class ?>" data-mage-init='{"relatedProducts":{"relatedCheckbox":".related.checkbox"}}' data-limit="<?= /* @escapeNotVerified */ $limit ?>" data-shuffle="<?= /* @escapeNotVerified */ $shuffle ?>"> - <?php else: ?> - <div class="block <?= /* @escapeNotVerified */ $class ?>" data-mage-init='{"upsellProducts":{}}' data-limit="<?= /* @escapeNotVerified */ $limit ?>" data-shuffle="<?= /* @escapeNotVerified */ $shuffle ?>"> +<?php if ($type == 'related' || $type == 'upsell') :?> +<?php if ($type == 'related') :?> +<div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"relatedProducts":{"relatedCheckbox":".related.checkbox"}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>"> + <?php else :?> + <div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"upsellProducts":{}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>"> <?php endif; ?> - <?php else: ?> - <div class="block <?= /* @escapeNotVerified */ $class ?>"> - <?php endif; ?> - <div class="block-title title"> - <strong id="block-<?= /* @escapeNotVerified */ $class ?>-heading" role="heading" aria-level="2"><?= /* @escapeNotVerified */ $title ?></strong> - </div> - <div class="block-content content" aria-labelledby="block-<?= /* @escapeNotVerified */ $class ?>-heading"> - <?php if ($type == 'related' && $canItemsAddToCart): ?> - <div class="block-actions"> - <?= /* @escapeNotVerified */ __('Check items to add to the cart or') ?> - <button type="button" class="action select" role="button"><span><?= /* @escapeNotVerified */ __('select all') ?></span></button> - </div> - <?php endif; ?> - <div class="products wrapper grid products-grid products-<?= /* @escapeNotVerified */ $type ?>"> - <ol class="products list items product-items"> - <?php foreach ($items as $_item): ?> - <?php $available = ''; ?> - <?php if (!$_item->isComposite() && $_item->isSaleable() && $type == 'related'): ?> - <?php if (!$_item->getRequiredOptions()): ?> - <?php $available = 'related-available'; ?> - <?php endif; ?> - <?php endif; ?> - <?php if ($type == 'related' || $type == 'upsell'): ?> - <li class="item product product-item" style="display: none;"> - <?php else: ?> - <li class="item product product-item"> + <?php else :?> + <div class="block <?= $block->escapeHtmlAttr($class) ?>"> + <?php endif; ?> + <div class="block-title title"> + <strong id="block-<?= $block->escapeHtmlAttr($class) ?>-heading" role="heading" aria-level="2"><?= $block->escapeHtml($title) ?></strong> + </div> + <div class="block-content content" aria-labelledby="block-<?= $block->escapeHtmlAttr($class) ?>-heading"> + <?php if ($type == 'related' && $canItemsAddToCart) :?> + <div class="block-actions"> + <?= $block->escapeHtml(__('Check items to add to the cart or')) ?> + <button type="button" class="action select" role="button"><span><?= $block->escapeHtml(__('select all')) ?></span></button> + </div> <?php endif; ?> - <div class="product-item-info <?= /* @escapeNotVerified */ $available ?>"> - <?= /* @escapeNotVerified */ '<!-- ' . $image . '-->' ?> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" class="product photo product-item-photo"> - <?= $block->getImage($_item, $image)->toHtml() ?> - </a> - <div class="product details product-item-details"> - <strong class="product name product-item-name"><a class="product-item-link" title="<?= $block->escapeHtml($_item->getName()) ?>" href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>"> - <?= $block->escapeHtml($_item->getName()) ?></a> - </strong> - - <?= /* @escapeNotVerified */ $block->getProductPrice($_item) ?> - - <?php if ($templateType): ?> - <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> - <?php endif; ?> - - <?php if ($canItemsAddToCart && !$_item->isComposite() && $_item->isSaleable() && $type == 'related'): ?> - <?php if (!$_item->getRequiredOptions()): ?> - <div class="field choice related"> - <input type="checkbox" class="checkbox related" id="related-checkbox<?= /* @escapeNotVerified */ $_item->getId() ?>" name="related_products[]" value="<?= /* @escapeNotVerified */ $_item->getId() ?>" /> - <label class="label" for="related-checkbox<?= /* @escapeNotVerified */ $_item->getId() ?>"><span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span></label> - </div> + <div class="products wrapper grid products-grid products-<?= $block->escapeHtmlAttr($type) ?>"> + <ol class="products list items product-items"> + <?php foreach ($items as $_item) :?> + <?php $available = ''; ?> + <?php if (!$_item->isComposite() && $_item->isSaleable() && $type == 'related') :?> + <?php if (!$_item->getRequiredOptions()) :?> + <?php $available = 'related-available'; ?> <?php endif; ?> <?php endif; ?> + <?php if ($type == 'related' || $type == 'upsell') :?> + <li class="item product product-item" style="display: none;"> + <?php else :?> + <li class="item product product-item"> + <?php endif; ?> + <div class="product-item-info <?= /* @noEscape */ $available ?>"> + <?= /* @noEscape */ '<!-- ' . $image . '-->' ?> + <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product photo product-item-photo"> + <?= $block->getImage($_item, $image)->toHtml() ?> + </a> + <div class="product details product-item-details"> + <strong class="product name product-item-name"><a class="product-item-link" title="<?= $block->escapeHtml($_item->getName()) ?>" href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>"> + <?= $block->escapeHtml($_item->getName()) ?></a> + </strong> + + <?= /* @noEscape */ $block->getProductPrice($_item) ?> + + <?php if ($templateType) :?> + <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> + <?php endif; ?> - <?php if ($showAddTo || $showCart): ?> - <div class="product actions product-item-actions"> - <?php if ($showCart): ?> - <div class="actions-primary"> - <?php if ($_item->isSaleable()): ?> - <?php if ($_item->getTypeInstance()->hasRequiredOptions($_item)): ?> - <button class="action tocart primary" data-mage-init='{"redirectUrl": {"url": "<?= /* @escapeNotVerified */ $block->getAddToCartUrl($_item) ?>"}}' type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> - </button> - <?php else: ?> - <?php $postDataHelper = $this->helper('Magento\Framework\Data\Helper\PostHelper'); - $postData = $postDataHelper->getPostData($block->getAddToCartUrl($_item), ['product' => $_item->getEntityId()]) - ?> - <button class="action tocart primary" - data-post='<?= /* @escapeNotVerified */ $postData ?>' - type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> - </button> - <?php endif; ?> - <?php else: ?> - <?php if ($_item->getIsSalable()): ?> - <div class="stock available"><span><?= /* @escapeNotVerified */ __('In stock') ?></span></div> - <?php else: ?> - <div class="stock unavailable"><span><?= /* @escapeNotVerified */ __('Out of stock') ?></span></div> - <?php endif; ?> - <?php endif; ?> - </div> + <?php if ($canItemsAddToCart && !$_item->isComposite() && $_item->isSaleable() && $type == 'related') :?> + <?php if (!$_item->getRequiredOptions()) :?> + <div class="field choice related"> + <input type="checkbox" class="checkbox related" id="related-checkbox<?= $block->escapeHtmlAttr($_item->getId()) ?>" name="related_products[]" value="<?= $block->escapeHtmlAttr($_item->getId()) ?>" /> + <label class="label" for="related-checkbox<?= $block->escapeHtmlAttr($_item->getId()) ?>"><span><?= $block->escapeHtml(__('Add to Cart')) ?></span></label> + </div> + <?php endif; ?> <?php endif; ?> - <?php if ($showAddTo): ?> - <div class="secondary-addto-links actions-secondary" data-role="add-to-links"> - <?php if ($addToBlock = $block->getChildBlock('addto')): ?> - <?= $addToBlock->setProduct($_item)->getChildHtml() ?> + <?php if ($showAddTo || $showCart) :?> + <div class="product actions product-item-actions"> + <?php if ($showCart) :?> + <div class="actions-primary"> + <?php if ($_item->isSaleable()) :?> + <?php if ($_item->getTypeInstance()->hasRequiredOptions($_item)) :?> + <button class="action tocart primary" data-mage-init='{"redirectUrl": {"url": "<?= $block->escapeUrl($block->getAddToCartUrl($_item)) ?>"}}' type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> + </button> + <?php else :?> + <?php $postDataHelper = $this->helper(Magento\Framework\Data\Helper\PostHelper::class); + $postData = $postDataHelper->getPostData($block->escapeUrl($block->getAddToCartUrl($_item)), ['product' => $_item->getEntityId()]) + ?> + <button class="action tocart primary" + data-post='<?= /* @noEscape */ $postData ?>' + type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> + </button> + <?php endif; ?> + <?php else :?> + <?php if ($_item->getIsSalable()) :?> + <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div> + <?php else :?> + <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div> + <?php endif; ?> + <?php endif; ?> + </div> + <?php endif; ?> + + <?php if ($showAddTo) :?> + <div class="secondary-addto-links actions-secondary" data-role="add-to-links"> + <?php if ($addToBlock = $block->getChildBlock('addto')) :?> + <?= $addToBlock->setProduct($_item)->getChildHtml() ?> + <?php endif; ?> + </div> <?php endif; ?> </div> <?php endif; ?> </div> - <?php endif; ?> - </div> - </div> - </li> - <?php endforeach ?> - </ol> + </div> + </li> + <?php endforeach ?> + </ol> + </div> + </div> </div> - </div> -</div> -<?php endif;?> + <?php endif;?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml index 02a6e999ad51f..b2ae8b9f7ab13 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,11 +10,13 @@ * * @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar */ -use Magento\Catalog\Model\Product\ProductList\Toolbar; + +// phpcs:disable Magento2.Security.IncludeFile.FoundIncludeFile +// phpcs:disable PSR2.Methods.FunctionCallSignature.SpaceBeforeOpenBracket ?> -<?php if ($block->getCollection()->getSize()): ?> - <div class="toolbar toolbar-products" data-mage-init='<?= /* @escapeNotVerified */ $block->getWidgetOptionsJson() ?>'> - <?php if ($block->isExpanded()): ?> +<?php if ($block->getCollection()->getSize()) :?> + <div class="toolbar toolbar-products" data-mage-init='<?= /* @noEscape */ $block->getWidgetOptionsJson() ?>'> + <?php if ($block->isExpanded()) :?> <?php include ($block->getTemplateFile('Magento_Catalog::product/list/toolbar/viewmode.phtml')) ?> <?php endif; ?> @@ -27,7 +26,7 @@ use Magento\Catalog\Model\Product\ProductList\Toolbar; <?php include ($block->getTemplateFile('Magento_Catalog::product/list/toolbar/limiter.phtml')) ?> - <?php if ($block->isExpanded()): ?> + <?php if ($block->isExpanded()) :?> <?php include ($block->getTemplateFile('Magento_Catalog::product/list/toolbar/sorter.phtml')) ?> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/amount.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/amount.phtml index b4ff1afa1c606..a8f504d6a4f17 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/amount.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/amount.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,19 +10,27 @@ * * @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar */ -use Magento\Catalog\Model\Product\ProductList\Toolbar; ?> <p class="toolbar-amount" id="toolbar-amount"> - <?php if ($block->getLastPageNum() > 1): ?> - <?php /* @escapeNotVerified */ echo __('Items %1-%2 of %3', - '<span class="toolbar-number">' . $block->getFirstNum() . '</span>', - '<span class="toolbar-number">' . $block->getLastNum() . '</span>', - '<span class="toolbar-number">' . $block->getTotalNum() . '</span>') ?> - <?php elseif ($block->getTotalNum() == 1): ?> - <?php /* @escapeNotVerified */ echo __('%1 Item', - '<span class="toolbar-number">' . $block->getTotalNum() . '</span>') ?> - <?php else: ?> - <?php /* @escapeNotVerified */ echo __('%1 Items', - '<span class="toolbar-number">' . $block->getTotalNum() . '</span>') ?> + <?php if ($block->getLastPageNum() > 1) :?> + <?= $block->escapeHtml( + __( + 'Items %1-%2 of %3', + '<span class="toolbar-number">' . $block->getFirstNum() . '</span>', + '<span class="toolbar-number">' . $block->getLastNum() . '</span>', + '<span class="toolbar-number">' . $block->getTotalNum() . '</span>' + ), + ['span'] + ) ?> + <?php elseif ($block->getTotalNum() == 1) :?> + <?= $block->escapeHtml( + __('%1 Item', '<span class="toolbar-number">' . $block->getTotalNum() . '</span>'), + ['span'] + ) ?> + <?php else :?> + <?= $block->escapeHtml( + __('%1 Items', '<span class="toolbar-number">' . $block->getTotalNum() . '</span>'), + ['span'] + ) ?> <?php endif; ?> </p> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml index ec4541bde5ca6..4ded219748c64 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,21 +10,22 @@ * * @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar */ -use Magento\Catalog\Model\Product\ProductList\Toolbar; ?> <div class="field limiter"> <label class="label" for="limiter"> - <span><?= /* @escapeNotVerified */ __('Show') ?></span> + <span><?= $block->escapeHtml(__('Show')) ?></span> </label> <div class="control"> <select id="limiter" data-role="limiter" class="limiter-options"> - <?php foreach ($block->getAvailableLimit() as $_key => $_limit): ?> - <option value="<?= /* @escapeNotVerified */ $_key ?>"<?php if ($block->isLimitCurrent($_key)): ?> - selected="selected"<?php endif ?>> - <?= /* @escapeNotVerified */ $_limit ?> + <?php foreach ($block->getAvailableLimit() as $_key => $_limit) :?> + <option value="<?= $block->escapeHtmlAttr($_key) ?>" + <?php if ($block->isLimitCurrent($_key)) :?> + selected="selected" + <?php endif ?>> + <?= $block->escapeHtml($_limit) ?> </option> <?php endforeach; ?> </select> </div> - <span class="limiter-text"><?= /* @escapeNotVerified */ __('per page') ?></span> + <span class="limiter-text"><?= $block->escapeHtml(__('per page')) ?></span> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/sorter.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/sorter.phtml index 92514c5b8ea50..58dde199998bc 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/sorter.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/sorter.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,14 +10,13 @@ * * @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar */ -use Magento\Catalog\Model\Product\ProductList\Toolbar; ?> <div class="toolbar-sorter sorter"> - <label class="sorter-label" for="sorter"><?= /* @escapeNotVerified */ __('Sort By') ?></label> + <label class="sorter-label" for="sorter"><?= $block->escapeHtml(__('Sort By')) ?></label> <select id="sorter" data-role="sorter" class="sorter-options"> - <?php foreach ($block->getAvailableOrders() as $_key => $_order): ?> - <option value="<?= /* @escapeNotVerified */ $_key ?>" - <?php if ($block->isOrderCurrent($_key)): ?> + <?php foreach ($block->getAvailableOrders() as $_key => $_order) :?> + <option value="<?= $block->escapeHtmlAttr($_key) ?>" + <?php if ($block->isOrderCurrent($_key)) :?> selected="selected" <?php endif; ?> > @@ -28,13 +24,21 @@ use Magento\Catalog\Model\Product\ProductList\Toolbar; </option> <?php endforeach; ?> </select> - <?php if ($block->getCurrentDirection() == 'desc'): ?> - <a title="<?= /* @escapeNotVerified */ __('Set Ascending Direction') ?>" href="#" class="action sorter-action sort-desc" data-role="direction-switcher" data-value="asc"> - <span><?= /* @escapeNotVerified */ __('Set Ascending Direction') ?></span> + <?php if ($block->getCurrentDirection() == 'desc') :?> + <a title="<?= $block->escapeHtmlAttr(__('Set Ascending Direction')) ?>" + href="#" + class="action sorter-action sort-desc" + data-role="direction-switcher" + data-value="asc"> + <span><?= $block->escapeHtml(__('Set Ascending Direction')) ?></span> </a> - <?php else: ?> - <a title="<?= /* @escapeNotVerified */ __('Set Descending Direction') ?>" href="#" class="action sorter-action sort-asc" data-role="direction-switcher" data-value="desc"> - <span><?= /* @escapeNotVerified */ __('Set Descending Direction') ?></span> + <?php else :?> + <a title="<?= $block->escapeHtmlAttr(__('Set Descending Direction')) ?>" + href="#" + class="action sorter-action sort-asc" + data-role="direction-switcher" + data-value="desc"> + <span><?= $block->escapeHtml(__('Set Descending Direction')) ?></span> </a> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/viewmode.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/viewmode.phtml index 366dfba71b0d1..955897f315d6f 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/viewmode.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/viewmode.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,32 +10,31 @@ * * @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar */ -use Magento\Catalog\Model\Product\ProductList\Toolbar; ?> -<?php if ($block->isEnabledViewSwitcher()): ?> -<div class="modes"> - <?php $_modes = $block->getModes(); ?> - <?php if ($_modes && count($_modes) > 1): ?> - <strong class="modes-label" id="modes-label"><?= /* @escapeNotVerified */ __('View as') ?></strong> - <?php foreach ($block->getModes() as $_code => $_label): ?> - <?php if ($block->isModeActive($_code)): ?> - <strong title="<?= /* @escapeNotVerified */ $_label ?>" - class="modes-mode active mode-<?= /* @escapeNotVerified */ strtolower($_code) ?>" - data-value="<?= /* @escapeNotVerified */ strtolower($_code) ?>"> - <span><?= /* @escapeNotVerified */ $_label ?></span> - </strong> - <?php else: ?> - <a class="modes-mode mode-<?= /* @escapeNotVerified */ strtolower($_code) ?>" - title="<?= /* @escapeNotVerified */ $_label ?>" - href="#" - data-role="mode-switcher" - data-value="<?= /* @escapeNotVerified */ strtolower($_code) ?>" - id="mode-<?= /* @escapeNotVerified */ strtolower($_code) ?>" - aria-labelledby="modes-label mode-<?= /* @escapeNotVerified */ strtolower($_code) ?>"> - <span><?= /* @escapeNotVerified */ $_label ?></span> - </a> - <?php endif; ?> - <?php endforeach; ?> - <?php endif; ?> -</div> +<?php if ($block->isEnabledViewSwitcher()) :?> + <div class="modes"> + <?php $_modes = $block->getModes(); ?> + <?php if ($_modes && count($_modes) > 1) :?> + <strong class="modes-label" id="modes-label"><?= $block->escapeHtml(__('View as')) ?></strong> + <?php foreach ($block->getModes() as $_code => $_label) :?> + <?php if ($block->isModeActive($_code)) :?> + <strong title="<?= $block->escapeHtmlAttr($_label) ?>" + class="modes-mode active mode-<?= $block->escapeHtmlAttr(strtolower($_code)) ?>" + data-value="<?= $block->escapeHtmlAttr(strtolower($_code)) ?>"> + <span><?= $block->escapeHtml($_label) ?></span> + </strong> + <?php else :?> + <a class="modes-mode mode-<?= $block->escapeHtmlAttr(strtolower($_code)) ?>" + title="<?= $block->escapeHtmlAttr($_label) ?>" + href="#" + data-role="mode-switcher" + data-value="<?= $block->escapeHtmlAttr(strtolower($_code)) ?>" + id="mode-<?= $block->escapeHtmlAttr(strtolower($_code)) ?>" + aria-labelledby="modes-label mode-<?= $block->escapeHtmlAttr(strtolower($_code)) ?>"> + <span><?= $block->escapeHtml($_label) ?></span> + </a> + <?php endif; ?> + <?php endforeach; ?> + <?php endif; ?> + </div> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml index f2d5e40cca4e5..b776fd4f7e193 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml @@ -3,28 +3,29 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable Magento2.Files.LineLength.MaxExceeded +// phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput + /** * Product list template * - * @see \Magento\Catalog\Block\Product\ListProduct + * @var $block \Magento\Catalog\Block\Product\ListProduct */ ?> <?php $start = microtime(true); $_productCollection = $block->getLoadedProductCollection(); -$_helper = $this->helper('Magento\Catalog\Helper\Output'); +$_helper = $this->helper(Magento\Catalog\Helper\Output::class); ?> -<?php if (!$_productCollection->count()): ?> -<p class="message note"><?= /* @escapeNotVerified */ __('We can\'t find products matching the selection.') ?></p> -<?php else: ?> -<?= $block->getToolbarHtml() ?> -<?= $block->getAdditionalHtml() ?> -<?php +<?php if (!$_productCollection->count()) :?> + <p class="message note"><?= $block->escapeHtml(__('We can\'t find products matching the selection.')) ?></p> +<?php else :?> + <?= $block->getToolbarHtml() ?> + <?= $block->getAdditionalHtml() ?> + <?php if ($block->getMode() == 'grid') { $viewMode = 'grid'; $image = 'category_page_grid'; @@ -36,65 +37,65 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); $showDescription = true; $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW; } -?> -<div class="products wrapper <?= /* @escapeNotVerified */ $viewMode ?>"> - <ol class="products list items"> - <?php foreach ($_productCollection as $_product): ?> - <li class="item product"> - <div class="product"> - <?php // Product Image ?> - <a href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" class="product photo"> - <?= $block->getImage($_product, $image)->toHtml() ?> - </a> - <div class="product details"> - <?php + ?> + <div class="products wrapper <?= /* @noEscape */ $viewMode ?>"> + <ol class="products list items"> + <?php foreach ($_productCollection as $_product) :?> + <li class="item product"> + <div class="product"> + <?php // Product Image ?> + <a href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" class="product photo"> + <?= $block->getImage($_product, $image)->toHtml() ?> + </a> + <div class="product details"> + <?php - $info = []; - $info['name'] = '<strong class="product name">' - . ' <a href="' . $_product->getProductUrl() . '" title="' - . $block->stripTags($_product->getName(), null, true) . '">' - . $_helper->productAttribute($_product, $_product->getName(), 'name') - . '</a></strong>'; - $info['price'] = $block->getProductPrice($_product); - $info['review'] = $block->getReviewsSummaryHtml($_product, $templateType); + $info = []; + $info['name'] = '<strong class="product name">' + . ' <a href="' . $block->escapeUrl($_product->getProductUrl()) . '" title="' + . $block->stripTags($_product->getName(), null, true) . '">' + . $_helper->productAttribute($_product, $_product->getName(), 'name') + . '</a></strong>'; + $info['price'] = $block->getProductPrice($_product); + $info['review'] = $block->getReviewsSummaryHtml($_product, $templateType); - if ($_product->isSaleable()) { - $info['button'] = '<button type="button" title="' . __('Add to Cart') . '" class="action tocart"' - . ' data-mage-init=\'{ "redirectUrl": { "event": "click", url: "' . $block->getAddToCartUrl($_product) . '"} }\'>' - . '<span>' . __('Add to Cart') . '</span></button>'; - } else { - $info['button'] = $_product->getIsSalable() ? '<div class="stock available"><span>' . __('In stock') . '</span></div>' : - '<div class="stock unavailable"><span>' . __('Out of stock') . '</span></div>'; - } + if ($_product->isSaleable()) { + $info['button'] = '<button type="button" title="' . $block->escapeHtmlAttr(__('Add to Cart')) . '" class="action tocart"' + . ' data-mage-init=\'{ "redirectUrl": { "event": "click", url: "' . $block->escapeUrl($block->getAddToCartUrl($_product)) . '"} }\'>' + . '<span>' . $block->escapeHtml(__('Add to Cart')) . '</span></button>'; + } else { + $info['button'] = $_product->getIsSalable() ? '<div class="stock available"><span>' . $block->escapeHtml(__('In stock')) . '</span></div>' : + '<div class="stock unavailable"><span>' . $block->escapeHtml(__('Out of stock')) . '</span></div>'; + } - $info['links'] = '<div class="product links" data-role="add-to-links">' - . '<a href="#" data-post=\'' . $this->helper('Magento\Wishlist\Helper\Data')->getAddParams($_product) . '\' class="action towishlist" data-action="add-to-wishlist">' - . '<span>' . __('Add to Wish List') . '</span></a>' - . '<a href="' . $block->getAddToCompareUrl($_product) . '" class="action tocompare">' - . '<span>' . __('Add to Compare') . '</span></a></div>'; - $info['actions'] = '<div class="product action">' . $info['button'] . $info['links'] . '</div>'; + $info['links'] = '<div class="product links" data-role="add-to-links">' + . '<a href="#" data-post=\'' . $this->helper(Magento\Wishlist\Helper\Data::class)->getAddParams($_product) . '\' class="action towishlist" data-action="add-to-wishlist">' + . '<span>' . $block->escapeHtml(__('Add to Wish List')) . '</span></a>' + . '<a href="' . $block->escapeUrl($block->getAddToCompareUrl($_product)) . '" class="action tocompare">' + . '<span>' . $block->escapeHtml(__('Add to Compare')) . '</span></a></div>'; + $info['actions'] = '<div class="product action">' . $info['button'] . $info['links'] . '</div>'; - if ($showDescription) { - $info['description'] = '<div class="product description">' - . $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description') - . ' <a href="' . $_product->getProductUrl() . '" class="action more">' - . __('Learn More') . '</a></div>'; - } else { - $info['description'] = ''; - } + if ($showDescription) { + $info['description'] = '<div class="product description">' + . $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description') + . ' <a href="' . $block->escapeUrl($_product->getProductUrl()) . '" class="action more">' + . $block->escapeHtml(__('Learn More')) . '</a></div>'; + } else { + $info['description'] = ''; + } - $details = $block->getInfoOrder() ?: ['name','price','review','description','actions']; - foreach ($details as $detail) { - /* @escapeNotVerified */ echo $info[$detail]; - } - ?> + $details = $block->getInfoOrder() ?: ['name','price','review','description','actions']; + foreach ($details as $detail) { + /* @noEscape */ echo $info[$detail]; + } + ?> + </div> </div> - </div> - </li> - <?php endforeach; ?> - </ol> -</div> -<?= $block->getToolbarHtml() ?> + </li> + <?php endforeach; ?> + </ol> + </div> + <?= $block->getToolbarHtml() ?> <?php endif; ?> -<?= /* @escapeNotVerified */ $time_taken = microtime(true) - $start ?> +<?= $time_taken = microtime(true) - $start ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/additional.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/additional.phtml index 2d89e24cc7aac..316fdb06592e2 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/additional.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/additional.phtml @@ -4,9 +4,8 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Catalog\Block\Product\View\Additional */ ?> -<?php foreach ($block->getChildHtmlList() as $_html): ?> - <?= /* @escapeNotVerified */ $_html ?> +<?php foreach ($block->getChildHtmlList() as $_html) :?> + <?= /* @noEscape */ $_html ?> <?php endforeach; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml index 0893cfab0bbf8..1924175764555 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Product\View*/ ?> <div class="product-addto-links" data-role="add-to-links"> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml index 194a472d81d58..9183e65181c48 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml @@ -4,15 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Product\View\Addto\Compare */ ?> <?php $viewModel = $block->getData('addToCompareViewModel'); ?> -<?php if ($viewModel->isAvailableForCompare($block->getProduct())): ?> -<a href="#" data-post='<?= /* @escapeNotVerified */ $block->getPostDataParams() ?>' +<?php if ($viewModel->isAvailableForCompare($block->getProduct())) :?> +<a href="#" data-post='<?= /* @noEscape */ $block->getPostDataParams() ?>' data-role="add-to-links" - class="action tocompare"><span><?= /* @escapeNotVerified */ __('Add to Compare') ?></span></a> + class="action tocompare"><span><?= $block->escapeHtml(__('Add to Compare')) ?></span></a> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml index 71452a2d65e97..f15824595f0ba 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml @@ -4,25 +4,23 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Product\View */ ?> <?php $_product = $block->getProduct(); ?> <?php $buttonTitle = __('Add to Cart'); ?> -<?php if ($_product->isSaleable()): ?> +<?php if ($_product->isSaleable()) :?> <div class="box-tocart"> <div class="fieldset"> - <?php if ($block->shouldRenderQuantity()): ?> + <?php if ($block->shouldRenderQuantity()) :?> <div class="field qty"> - <label class="label" for="qty"><span><?= /* @escapeNotVerified */ __('Qty') ?></span></label> + <label class="label" for="qty"><span><?= $block->escapeHtml(__('Qty')) ?></span></label> <div class="control"> <input type="number" name="qty" id="qty" min="0" - value="<?= /* @escapeNotVerified */ $block->getProductDefaultQty() * 1 ?>" - title="<?= /* @escapeNotVerified */ __('Qty') ?>" + value="<?= $block->getProductDefaultQty() * 1 ?>" + title="<?= $block->escapeHtmlAttr(__('Qty')) ?>" class="input-text qty" data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>" /> @@ -31,10 +29,10 @@ <?php endif; ?> <div class="actions"> <button type="submit" - title="<?= /* @escapeNotVerified */ $buttonTitle ?>" + title="<?= $block->escapeHtmlAttr($buttonTitle) ?>" class="action primary tocart" id="product-addtocart-button" disabled> - <span><?= /* @escapeNotVerified */ $buttonTitle ?></span> + <span><?= $block->escapeHtml($buttonTitle) ?></span> </button> <?= $block->getChildHtml('', true) ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml index 2435590786807..a045a21e55d27 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/attribute.phtml @@ -4,16 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** * Product view template * - * @see \Magento\Catalog\Block\Product\View\Description + * @var $block \Magento\Catalog\Block\Product\View\Description */ ?> <?php -$_helper = $this->helper('Magento\Catalog\Helper\Output'); +$_helper = $this->helper(Magento\Catalog\Helper\Output::class); $_product = $block->getProduct(); if (!$_product instanceof \Magento\Catalog\Model\Product) { @@ -37,15 +37,19 @@ if ($_attributeLabel && $_attributeLabel == 'default') { $_attributeLabel = $_product->getResource()->getAttribute($_code)->getStoreLabel(); } if ($_attributeType && $_attributeType == 'text') { - $_attributeValue = ($_helper->productAttribute($_product, $_product->$_call(), $_code)) ? $_product->getAttributeText($_code) : ''; + $_attributeValue = ($_helper->productAttribute($_product, $_product->$_call(), $_code)) + ? $_product->getAttributeText($_code) + : ''; } else { $_attributeValue = $_helper->productAttribute($_product, $_product->$_call(), $_code); } ?> -<?php if ($_attributeValue): ?> -<div class="product attribute <?= /* @escapeNotVerified */ $_className ?>"> - <?php if ($renderLabel): ?><strong class="type"><?= /* @escapeNotVerified */ $_attributeLabel ?></strong><?php endif; ?> - <div class="value" <?= /* @escapeNotVerified */ $_attributeAddAttribute ?>><?= /* @escapeNotVerified */ $_attributeValue ?></div> +<?php if ($_attributeValue) :?> +<div class="product attribute <?= $block->escapeHtmlAttr($_className) ?>"> + <?php if ($renderLabel) :?> + <strong class="type"><?= $block->escapeHtml($_attributeLabel) ?></strong> + <?php endif; ?> + <div class="value" <?= /* @noEscape */ $_attributeAddAttribute ?>><?= /* @noEscape */ $_attributeValue ?></div> </div> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml index 1c4a37fedebe3..a4f0fb3efab9e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** * Product additional attributes template @@ -13,18 +13,18 @@ */ ?> <?php - $_helper = $this->helper('Magento\Catalog\Helper\Output'); + $_helper = $this->helper(Magento\Catalog\Helper\Output::class); $_product = $block->getProduct(); ?> -<?php if ($_additional = $block->getAdditionalData()): ?> +<?php if ($_additional = $block->getAdditionalData()) :?> <div class="additional-attributes-wrapper table-wrapper"> <table class="data table additional-attributes" id="product-attribute-specs-table"> - <caption class="table-caption"><?= /* @escapeNotVerified */ __('More Information') ?></caption> + <caption class="table-caption"><?= $block->escapeHtml(__('More Information')) ?></caption> <tbody> - <?php foreach ($_additional as $_data): ?> + <?php foreach ($_additional as $_data) :?> <tr> <th class="col label" scope="row"><?= $block->escapeHtml($_data['label']) ?></th> - <td class="col data" data-th="<?= $block->escapeHtml($_data['label']) ?>"><?= /* @escapeNotVerified */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?></td> + <td class="col data" data-th="<?= $block->escapeHtmlAttr($_data['label']) ?>"><?= /* @noEscape */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?></td> </tr> <?php endforeach; ?> </tbody> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/counter.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/counter.phtml index 4414214f99a6e..a4aa675b2c346 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/counter.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/counter.phtml @@ -13,7 +13,7 @@ { "*": { "Magento_Catalog/js/product/view/provider": { - "data": <?= /* @escapeNotVerified */ $block->getCurrentProductData() ?> + "data": <?= /* @noEscape */ $block->getCurrentProductData() ?> } } } diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml index b5cdd1a2a31ba..c08c4d771b34a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** * Product description template @@ -12,4 +12,8 @@ * @var $block \Magento\Catalog\Block\Product\View\Description */ ?> -<?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Output')->productAttribute($block->getProduct(), $block->getProduct()->getDescription(), 'description') ?> +<?= /* @noEscape */ $this->helper(Magento\Catalog\Helper\Output::class)->productAttribute( + $block->getProduct(), + $block->getProduct()->getDescription(), + 'description' +) ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml index 57eabbf1d8c8a..f4de22f98b021 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml @@ -4,36 +4,34 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Catalog\Block\Product\View\Details $block */ ?> -<?php if ($detailedInfoGroup = $block->getGroupSortedChildNames('detailed_info', 'getChildHtml')):?> +<?php if ($detailedInfoGroup = $block->getGroupSortedChildNames('detailed_info', 'getChildHtml')) :?> <div class="product info detailed"> <?php $layout = $block->getLayout(); ?> <div class="product data items" data-mage-init='{"tabs":{"openedState":"active"}}'> - <?php foreach ($detailedInfoGroup as $name):?> + <?php foreach ($detailedInfoGroup as $name) :?> <?php - $html = $layout->renderElement($name); - if (!trim($html)) { - continue; - } - $alias = $layout->getElementAlias($name); - $label = $block->getChildData($alias, 'title'); + $html = $layout->renderElement($name); + if (!trim($html)) { + continue; + } + $alias = $layout->getElementAlias($name); + $label = $block->getChildData($alias, 'title'); ?> <div class="data item title" - data-role="collapsible" id="tab-label-<?= /* @escapeNotVerified */ $alias ?>"> + data-role="collapsible" id="tab-label-<?= $block->escapeHtmlAttr($alias) ?>"> <a class="data switch" tabindex="-1" data-toggle="trigger" - href="#<?= /* @escapeNotVerified */ $alias ?>" - id="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title"> - <?= /* @escapeNotVerified */ $label ?> + href="#<?= $block->escapeUrl($alias) ?>" + id="tab-label-<?= $block->escapeHtmlAttr($alias) ?>-title"> + <?= /* @noEscape */ $label ?> </a> </div> - <div class="data item content" - aria-labelledby="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title" id="<?= /* @escapeNotVerified */ $alias ?>" data-role="content"> - <?= /* @escapeNotVerified */ $html ?> + <div class="data item content" + aria-labelledby="tab-label-<?= $block->escapeHtmlAttr($alias) ?>-title" id="<?= $block->escapeHtmlAttr($alias) ?>" data-role="content"> + <?= /* @noEscape */ $html ?> </div> <?php endforeach;?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml index b6091f13af191..7d59e9831e947 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/form.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** * Product view template @@ -12,28 +12,28 @@ * @var $block \Magento\Catalog\Block\Product\View */ ?> -<?php $_helper = $this->helper('Magento\Catalog\Helper\Output'); ?> +<?php $_helper = $this->helper(Magento\Catalog\Helper\Output::class); ?> <?php $_product = $block->getProduct(); ?> <div class="product-add-form"> <form data-product-sku="<?= $block->escapeHtml($_product->getSku()) ?>" - action="<?= /* @NoEscape */ $block->getSubmitUrl($_product) ?>" method="post" - id="product_addtocart_form"<?php if ($_product->getOptions()): ?> enctype="multipart/form-data"<?php endif; ?>> - <input type="hidden" name="product" value="<?= /* @escapeNotVerified */ $_product->getId() ?>" /> + action="<?= $block->escapeUrl($block->getSubmitUrl($_product)) ?>" method="post" + id="product_addtocart_form"<?php if ($_product->getOptions()) :?> enctype="multipart/form-data"<?php endif; ?>> + <input type="hidden" name="product" value="<?= (int)$_product->getId() ?>" /> <input type="hidden" name="selected_configurable_option" value="" /> <input type="hidden" name="related_product" id="related-products-field" value="" /> - <input type="hidden" name="item" value="<?= $block->escapeHtmlAttr($block->getRequest()->getParam('id')) ?>" /> + <input type="hidden" name="item" value="<?= (int)$block->getRequest()->getParam('id') ?>" /> <?= $block->getBlockHtml('formkey') ?> <?= $block->getChildHtml('form_top') ?> - <?php if (!$block->hasOptions()):?> + <?php if (!$block->hasOptions()) :?> <?= $block->getChildHtml('product_info_form_content') ?> - <?php else:?> - <?php if ($_product->isSaleable() && $block->getOptionsContainer() == 'container1'):?> + <?php else :?> + <?php if ($_product->isSaleable() && $block->getOptionsContainer() == 'container1') :?> <?= $block->getChildChildHtml('options_container') ?> <?php endif;?> <?php endif; ?> - <?php if ($_product->isSaleable() && $block->hasOptions() && $block->getOptionsContainer() == 'container2'):?> + <?php if ($_product->isSaleable() && $block->hasOptions() && $block->getOptionsContainer() == 'container2') :?> <?= $block->getChildChildHtml('options_container') ?> <?php endif;?> <?= $block->getChildHtml('form_bottom') ?> @@ -52,6 +52,6 @@ return !$(elem).find('.price-from').length; }); - priceBoxes.priceBox({'priceConfig': <?= /* @escapeNotVerified */ $block->getJsonConfig() ?>}); + priceBoxes.priceBox({'priceConfig': <?= /* @noEscape */ $block->getJsonConfig() ?>}); }); </script> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml index 1f06b90758d0b..4b33864aef47a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * Product media data template * @@ -14,19 +12,19 @@ ?> <?php - $images = $block->getGalleryImages()->getItems(); - $mainImage = current(array_filter($images, function ($img) use ($block) { - return $block->isMainImage($img); - })); +$images = $block->getGalleryImages()->getItems(); +$mainImage = current(array_filter($images, function ($img) use ($block) { + return $block->isMainImage($img); +})); - if (!empty($images) && empty($mainImage)) { - $mainImage = $block->getGalleryImages()->getFirstItem(); - } +if (!empty($images) && empty($mainImage)) { + $mainImage = $block->getGalleryImages()->getFirstItem(); +} - $helper = $block->getData('imageHelper'); - $mainImageData = $mainImage ? - $mainImage->getData('medium_image_url') : - $helper->getDefaultPlaceholderUrl('image'); +$helper = $block->getData('imageHelper'); +$mainImageData = $mainImage ? + $mainImage->getData('medium_image_url') : + $helper->getDefaultPlaceholderUrl('image'); ?> @@ -43,11 +41,11 @@ "[data-gallery-role=gallery-placeholder]": { "mage/gallery/gallery": { "mixins":["magnifier/magnify"], - "magnifierOpts": <?= /* @escapeNotVerified */ $block->getMagnifier() ?>, - "data": <?= /* @escapeNotVerified */ $block->getGalleryImagesJson() ?>, + "magnifierOpts": <?= /* @noEscape */ $block->getMagnifier() ?>, + "data": <?= /* @noEscape */ $block->getGalleryImagesJson() ?>, "options": <?= /* @noEscape */ $block->getGalleryOptions()->getOptionsJson() ?>, "fullscreen": <?= /* @noEscape */ $block->getGalleryOptions()->getFSOptionsJson() ?>, - "breakpoints": <?= /* @escapeNotVerified */ $block->getBreakpoints() ?> + "breakpoints": <?= /* @noEscape */ $block->getBreakpoints() ?> } } } diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml index d52b594ededdf..f57c9b68ddbd2 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml @@ -4,11 +4,10 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php $_product = $block->getProduct() ?> -<?php if ($block->canEmailToFriend()): ?> - <a href="<?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Product')->getEmailToFriendUrl($_product) ?>" - class="action mailto friend"><span><?= /* @escapeNotVerified */ __('Email') ?></span></a> +<?php if ($block->canEmailToFriend()) :?> + <a href="<?= $block->escapeUrl($this->helper(Magento\Catalog\Helper\Product::class)->getEmailToFriendUrl($_product)) ?>" + class="action mailto friend"><span><?= $block->escapeHtml(__('Email')) ?></span></a> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/currency.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/currency.phtml index 87655797f40e5..7f14b71a60c7a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/currency.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/currency.phtml @@ -4,8 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Directory\Block\Currency */ ?> -<meta property="product:price:currency" content="<?= /* @escapeNotVerified */ $block->stripTags($block->getCurrentCurrencyCode()) ?>"/> +<meta property="product:price:currency" + content="<?= /* @noEscape */ $block->stripTags($block->getCurrentCurrencyCode()) ?>"/> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml index 40f86c7e68d6c..eb2bde647f9b1 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml @@ -4,17 +4,18 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Product\View */ ?> <meta property="og:type" content="product" /> -<meta property="og:title" content="<?= $block->escapeHtmlAttr($block->stripTags($block->getProduct()->getName())) ?>" /> -<meta property="og:image" content="<?= $block->escapeUrl($block->getImage($block->getProduct(), 'product_base_image')->getImageUrl()) ?>" /> -<meta property="og:description" content="<?= $block->escapeHtmlAttr($block->stripTags($block->getProduct()->getShortDescription())) ?>" /> +<meta property="og:title" + content="<?= /* @noEscape */ $block->stripTags($block->getProduct()->getName()) ?>" /> +<meta property="og:image" + content="<?= $block->escapeUrl($block->getImage($block->getProduct(), 'product_base_image')->getImageUrl()) ?>" /> +<meta property="og:description" + content="<?= /* @noEscape */ $block->stripTags($block->getProduct()->getShortDescription()) ?>" /> <meta property="og:url" content="<?= $block->escapeUrl($block->getProduct()->getProductUrl()) ?>" /> -<?php if ($priceAmount = $block->getProduct()->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()):?> - <meta property="product:price:amount" content="<?= /* @escapeNotVerified */ $priceAmount ?>"/> +<?php if ($priceAmount = $block->getProduct()->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()) :?> + <meta property="product:price:amount" content="<?= $block->escapeHtmlAttr($priceAmount) ?>"/> <?= $block->getChildHtml('meta.currency') ?> <?php endif;?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options.phtml index 3ebfa76860950..d9a0c845b9f83 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options.phtml @@ -4,26 +4,24 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\Catalog\Block\Product\View\Options */ ?> <?php $_options = $block->decorateArray($block->getOptions()) ?> <?php $_productId = $block->getProduct()->getId() ?> -<?php if (count($_options)):?> +<?php if (count($_options)) :?> <script type="text/x-magento-init"> { "#product_addtocart_form": { "priceOptions": { - "optionConfig": <?= /* @escapeNotVerified */ $block->getJsonConfig() ?>, + "optionConfig": <?= /* @noEscape */ $block->getJsonConfig() ?>, "controlContainer": ".field", "priceHolderSelector": "[data-product-id='<?= $block->escapeHtml($_productId) ?>'][data-role=priceBox]" } } } </script> - <?php foreach ($_options as $_option): ?> + <?php foreach ($_options as $_option) :?> <?= $block->getOptionHtml($_option) ?> <?php endforeach; ?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/date.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/date.phtml index 66895fa1eabf9..b7cd64277fe40 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/date.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/date.phtml @@ -3,46 +3,43 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Date */ ?> <?php $_option = $block->getOption() ?> -<?php $_optionId = $_option->getId() ?> +<?php $_optionId = $block->escapeHtmlAttr($_option->getId()) ?> <?php $class = ($_option->getIsRequire()) ? ' required' : ''; ?> -<div class="field date<?= /* @escapeNotVerified */ $class ?>" +<div class="field date<?= /* @noEscape */ $class ?>" data-mage-init='{"priceOptionDate":{"fromSelector":"#product_addtocart_form"}}'> - <fieldset class="fieldset fieldset-product-options-inner<?= /* @escapeNotVerified */ $class ?>"> + <fieldset class="fieldset fieldset-product-options-inner<?= /* @noEscape */ $class ?>"> <legend class="legend"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> </legend> <div class="control"> <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME - || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE): ?> + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE) :?> <?= $block->getDateHtml() ?> <?php endif; ?> <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME - || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME): ?> + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME) :?> <?= $block->getTimeHtml() ?> <?php endif; ?> - <?php if ($_option->getIsRequire()): ?> + <?php if ($_option->getIsRequire()) :?> <input type="hidden" - name="validate_datetime_<?= /* @escapeNotVerified */ $_optionId ?>" - class="validate-datetime-<?= /* @escapeNotVerified */ $_optionId ?>" + name="validate_datetime_<?= /* @noEscape */ $_optionId ?>" + class="validate-datetime-<?= /* @noEscape */ $_optionId ?>" value="" - data-validate="{'validate-required-datetime':<?= /* @escapeNotVerified */ $_optionId ?>}"/> - <?php else: ?> + data-validate="{'validate-required-datetime':<?= /* @noEscape */ $_optionId ?>}"/> + <?php else :?> <input type="hidden" - name="validate_datetime_<?= /* @escapeNotVerified */ $_optionId ?>" - class="validate-datetime-<?= /* @escapeNotVerified */ $_optionId ?>" + name="validate_datetime_<?= /* @noEscape */ $_optionId ?>" + class="validate-datetime-<?= /* @noEscape */ $_optionId ?>" value="" - data-validate="{'validate-optional-datetime':<?= /* @escapeNotVerified */ $_optionId ?>}"/> + data-validate="{'validate-optional-datetime':<?= /* @noEscape */ $_optionId ?>}"/> <?php endif; ?> <script type="text/x-magento-init"> { diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/default.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/default.phtml index 2006bf6e9f414..c25dab8b70a5c 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/default.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/default.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_option = $block->getOption() ?> <div class="field"> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml index adb729c6d86ec..e83e55ad2a03c 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml @@ -3,65 +3,62 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\File */ ?> <?php $_option = $block->getOption(); ?> <?php $_fileInfo = $block->getFileInfo(); ?> <?php $_fileExists = $_fileInfo->hasData(); ?> -<?php $_fileName = 'options_' . $_option->getId() . '_file'; ?> +<?php $_fileName = 'options_' . $block->escapeHtmlAttr($_option->getId()) . '_file'; ?> <?php $_fieldNameAction = $_fileName . '_action'; ?> <?php $_fieldValueAction = $_fileExists ? 'save_old' : 'save_new'; ?> <?php $_fileNamed = $_fileName . '_name'; ?> <?php $class = ($_option->getIsRequire()) ? ' required' : ''; ?> -<div class="field file<?= /* @escapeNotVerified */ $class ?>"> +<div class="field file<?= /* @noEscape */ $class ?>"> <label class="label" for="<?= /* @noEscape */ $_fileName ?>" id="<?= /* @noEscape */ $_fileName ?>-label"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> </label> - <?php if ($_fileExists): ?> + <?php if ($_fileExists) :?> <div class="control"> <span class="<?= /* @noEscape */ $_fileNamed ?>"><?= $block->escapeHtml($_fileInfo->getTitle()) ?></span> <a href="javascript:void(0)" class="label" id="change-<?= /* @noEscape */ $_fileName ?>" > - <?= /* @escapeNotVerified */ __('Change') ?> + <?= $block->escapeHtml(__('Change')) ?> </a> - <?php if (!$_option->getIsRequire()): ?> - <input type="checkbox" id="delete-<?= /* @escapeNotVerified */ $_fileName ?>" /> - <span class="label"><?= /* @escapeNotVerified */ __('Delete') ?></span> + <?php if (!$_option->getIsRequire()) :?> + <input type="checkbox" id="delete-<?= /* @noEscape */ $_fileName ?>" /> + <span class="label"><?= $block->escapeHtml(__('Delete')) ?></span> <?php endif; ?> </div> <?php endif; ?> - <div class="control" id="input-box-<?= /* @escapeNotVerified */ $_fileName ?>" + <div class="control" id="input-box-<?= /* @noEscape */ $_fileName ?>" data-mage-init='{"priceOptionFile":{ "fileName":"<?= /* @noEscape */ $_fileName ?>", "fileNamed":"<?= /* @noEscape */ $_fileNamed ?>", - "fieldNameAction":"<?= /* @escapeNotVerified */ $_fieldNameAction ?>", - "changeFileSelector":"#change-<?= /* @escapeNotVerified */ $_fileName ?>", - "deleteFileSelector":"#delete-<?= /* @escapeNotVerified */ $_fileName ?>"} + "fieldNameAction":"<?= /* @noEscape */ $_fieldNameAction ?>", + "changeFileSelector":"#change-<?= /* @noEscape */ $_fileName ?>", + "deleteFileSelector":"#delete-<?= /* @noEscape */ $_fileName ?>"} }' <?= $_fileExists ? 'style="display:none"' : '' ?>> <input type="file" - name="<?= /* @escapeNotVerified */ $_fileName ?>" - id="<?= /* @escapeNotVerified */ $_fileName ?>" + name="<?= /* @noEscape */ $_fileName ?>" + id="<?= /* @noEscape */ $_fileName ?>" class="product-custom-option<?= $_option->getIsRequire() ? ' required' : '' ?>" - <?= $_fileExists ? 'disabled="disabled"' : '' ?> /> - <input type="hidden" name="<?= /* @escapeNotVerified */ $_fieldNameAction ?>" value="<?= /* @escapeNotVerified */ $_fieldValueAction ?>" /> - <?php if ($_option->getFileExtension()): ?> + <?= $_fileExists ? 'disabled="disabled"' : '' ?> /> + <input type="hidden" name="<?= /* @noEscape */ $_fieldNameAction ?>" value="<?= /* @noEscape */ $_fieldValueAction ?>" /> + <?php if ($_option->getFileExtension()) :?> <p class="note"> - <?= /* @escapeNotVerified */ __('Compatible file extensions to upload') ?>: <strong><?= /* @escapeNotVerified */ $_option->getFileExtension() ?></strong> + <?= $block->escapeHtml(__('Compatible file extensions to upload')) ?>: <strong><?= $block->escapeHtml($_option->getFileExtension()) ?></strong> </p> <?php endif; ?> - <?php if ($_option->getImageSizeX() > 0): ?> + <?php if ($_option->getImageSizeX() > 0) :?> <p class="note"> - <?= /* @escapeNotVerified */ __('Maximum image width') ?>: <strong><?= /* @escapeNotVerified */ $_option->getImageSizeX() ?> <?= /* @escapeNotVerified */ __('px.') ?></strong> + <?= $block->escapeHtml(__('Maximum image width')) ?>: <strong><?= (int)$_option->getImageSizeX() ?> <?= $block->escapeHtml(__('px.')) ?></strong> </p> <?php endif; ?> - <?php if ($_option->getImageSizeY() > 0): ?> + <?php if ($_option->getImageSizeY() > 0) :?> <p class="note"> - <?= /* @escapeNotVerified */ __('Maximum image height') ?>: <strong><?= /* @escapeNotVerified */ $_option->getImageSizeY() ?> <?= /* @escapeNotVerified */ __('px.') ?></strong> + <?= $block->escapeHtml(__('Maximum image height')) ?>: <strong><?= (int)$_option->getImageSizeY() ?> <?= $block->escapeHtml(__('px.')) ?></strong> </p> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/select.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/select.phtml index 980b78f917cf2..c4c1d24423bb0 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/select.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/select.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Select */ ?> @@ -13,15 +10,15 @@ $_option = $block->getOption(); $class = ($_option->getIsRequire()) ? ' required' : ''; ?> -<div class="field<?= /* @escapeNotVerified */ $class ?>"> - <label class="label" for="select_<?= /* @escapeNotVerified */ $_option->getId() ?>"> +<div class="field<?= /* @noEscape */ $class ?>"> + <label class="label" for="select_<?= $block->escapeHtmlAttr($_option->getId()) ?>"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> </label> <div class="control"> <?= $block->getValuesHtml() ?> - <?php if ($_option->getIsRequire()): ?> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX): ?> - <span id="options-<?= /* @escapeNotVerified */ $_option->getId() ?>-container"></span> + <?php if ($_option->getIsRequire()) :?> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX) :?> + <span id="options-<?= $block->escapeHtmlAttr($_option->getId()) ?>-container"></span> <?php endif; ?> <?php endif;?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml index a04e366a43a2d..dd4c000d1f338 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/text.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\Options\Type\Text */ ?> <?php @@ -15,14 +12,14 @@ $class = ($_option->getIsRequire()) ? ' required' : ''; <div class="field<?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA) { echo ' textarea'; -} ?><?= /* @escapeNotVerified */ $class ?>"> - <label class="label" for="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text"> +} ?><?= /* @noEscape */ $class ?>"> + <label class="label" for="options_<?= $block->escapeHtmlAttr($_option->getId()) ?>_text"> <span><?= $block->escapeHtml($_option->getTitle()) ?></span> - <?= /* @escapeNotVerified */ $block->getFormattedPrice() ?> + <?= /* @noEscape */ $block->getFormattedPrice() ?> </label> <div class="control"> - <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD): ?> + <?php if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD) :?> <?php $_textValidate = null; if ($_option->getIsRequire()) { $_textValidate['required'] = true; @@ -33,15 +30,15 @@ $class = ($_option->getIsRequire()) ? ' required' : ''; $_textValidate['validate-no-utf8mb4-characters'] = true; ?> <input type="text" - id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text" + id="options_<?= $block->escapeHtmlAttr($_option->getId()) ?>_text" class="input-text product-custom-option" - <?php if (!empty($_textValidate)) {?> - data-validate="<?= $block->escapeHtml(json_encode($_textValidate)) ?>" - <?php } ?> - name="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" + <?php if (!empty($_textValidate)) {?> + data-validate="<?= $block->escapeHtml(json_encode($_textValidate)) ?>" + <?php } ?> + name="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" value="<?= $block->escapeHtml($block->getDefaultValue()) ?>"/> - <?php elseif ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA): ?> + <?php elseif ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA) :?> <?php $_textAreaValidate = null; if ($_option->getIsRequire()) { $_textAreaValidate['required'] = true; @@ -51,31 +48,31 @@ $class = ($_option->getIsRequire()) ? ' required' : ''; } $_textAreaValidate['validate-no-utf8mb4-characters'] = true; ?> - <textarea id="options_<?= /* @escapeNotVerified */ $_option->getId() ?>_text" + <textarea id="options_<?= $block->escapeHtmlAttr($_option->getId()) ?>_text" class="product-custom-option" <?php if (!empty($_textAreaValidate)) {?> data-validate="<?= $block->escapeHtml(json_encode($_textAreaValidate)) ?>" <?php } ?> - name="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" - data-selector="options[<?= /* @escapeNotVerified */ $_option->getId() ?>]" + name="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" + data-selector="options[<?= $block->escapeHtmlAttr($_option->getId()) ?>]" rows="5" cols="25"><?= $block->escapeHtml($block->getDefaultValue()) ?></textarea> <?php endif; ?> - <?php if ($_option->getMaxCharacters()): ?> - <p class="note note_<?= /* @escapeNotVerified */ $_option->getId() ?>"> - <?= /* @escapeNotVerified */ __('Maximum %1 characters', $_option->getMaxCharacters()) ?> + <?php if ($_option->getMaxCharacters()) :?> + <p class="note note_<?= $block->escapeHtmlAttr($_option->getId()) ?>"> + <?= $block->escapeHtml(__('Maximum %1 characters', $_option->getMaxCharacters())) ?> <span class="character-counter no-display"></span> </p> <?php endif; ?> </div> - <?php if ($_option->getMaxCharacters()): ?> + <?php if ($_option->getMaxCharacters()) :?> <script type="text/x-magento-init"> { - "[data-selector='options[<?= /* @escapeNotVerified */ $_option->getId() ?>]']": { + "[data-selector='options[<?= $block->escapeJs($_option->getId()) ?>]']": { "Magento_Catalog/js/product/remaining-characters": { - "maxLength": "<?= /* @escapeNotVerified */ $_option->getMaxCharacters() ?>", - "noteSelector": ".note_<?= /* @escapeNotVerified */ $_option->getId() ?>", - "counterSelector": ".note_<?= /* @escapeNotVerified */ $_option->getId() ?> .character-counter" + "maxLength": "<?= (int)$_option->getMaxCharacters() ?>", + "noteSelector": ".note_<?= $block->escapeJs($_option->getId()) ?>", + "counterSelector": ".note_<?= $block->escapeJs($_option->getId()) ?> .character-counter" } } } diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml index ca6960a215a7a..88ee45bafe731 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml @@ -3,14 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +/** @var $block Magento\Catalog\Block\Product\View */ ?> <?php $required = ''; if ($block->hasRequiredOptions()) { - $required = ' data-hasrequired="' . __('* Required Fields') . '"'; + $required = ' data-hasrequired="' . $block->escapeHtmlAttr(__('* Required Fields')) . '"'; } ?> -<div class="product-options-wrapper" id="product-options-wrapper"<?= /* @escapeNotVerified */ $required ?>> +<div class="product-options-wrapper" id="product-options-wrapper"<?= /* @noEscape */ $required ?>> <div class="fieldset" tabindex="0"> <?= $block->getChildHtml('', true) ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/price_clone.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/price_clone.phtml index e8c0b32fd7692..979bab167c344 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/price_clone.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/price_clone.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var \Magento\Catalog\Block\Product\AbstractProduct $block */ ?> <?php $_product = $block->getProduct() ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/review.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/review.phtml index 5575d00df7457..5250673436648 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/review.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/review.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Catalog\Block\Product\AbstractProduct */ ?> <?= $block->getReviewsSummaryHtml($block->getProduct(), false, true) ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/type/default.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/type/default.phtml index 7e522b4f88306..30edb2df03754 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/type/default.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/type/default.phtml @@ -3,21 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Catalog\Block\Product\View\AbstractView */?> <?php $_product = $block->getProduct() ?> -<?php if ($block->displayProductStockStatus()): ?> - <?php if ($_product->isAvailable()): ?> - <div class="stock available" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('In stock') ?></span> +<?php if ($block->displayProductStockStatus()) :?> + <?php if ($_product->isAvailable()) :?> + <div class="stock available" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('In stock')) ?></span> </div> - <?php else: ?> - <div class="stock unavailable" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('Out of stock') ?></span> + <?php else :?> + <div class="stock unavailable" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('Out of stock')) ?></span> </div> <?php endif; ?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/grid.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/grid.phtml index e0550cc7d4414..a2187b685ca0e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/grid.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/grid.phtml @@ -1,18 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - - //@codingStandardsIgnoreFile ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?php /* @escapeNotVerified */ echo $block->renderApp( +<?php /* @noEscape */ echo $block->renderApp( [ 'widget_columns' => [ 'displayMode' => 'grid' diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/list.phtml index 3a4f81d946bfd..5c5f6493c3163 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/list.phtml @@ -3,16 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - - //@codingStandardsIgnoreFile ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?php /* @escapeNotVerified */ echo $block->renderApp( +<?php /* @noEscape */ echo $block->renderApp( [ 'widget_columns' => [ 'displayMode' => 'list' diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml index 2d2c91aadd473..8db3cc80368e2 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml @@ -3,16 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - - //@codingStandardsIgnoreFile ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?php /* @escapeNotVerified */ echo $block->renderApp( +<?php /* @noEscape */ echo $block->renderApp( [ 'listing' => [ 'displayMode' => 'grid' diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_block.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_block.phtml index 2ec671b8de3ab..69f0319134ea0 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_block.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_block.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile ?> <div class="widget block block-product-link"> - <a <?= /* @escapeNotVerified */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> + <a <?= /* @noEscape */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_inline.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_inline.phtml index 373eda1117455..8d9f6500894b4 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_inline.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/link/link_inline.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile ?> <span class="widget block block-product-link-inline"> - <a <?= /* @escapeNotVerified */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> + <a <?= /* @noEscape */ $block->getLinkAttributes() ?>><span><?= $block->escapeHtml($block->getLabel()) ?></span></a> </span> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml index 45a206f3f92bf..53a0682311b1f 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml @@ -4,60 +4,61 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable Magento2.Files.LineLength.MaxExceeded ?> -<?php if (($_products = $block->getProductCollection()) && $_products->getSize()): ?> +<?php if (($_products = $block->getProductCollection()) && $_products->getSize()) :?> <div class="block widget block-new-products-list"> <div class="block-title"> - <strong><?= /* @escapeNotVerified */ __('New Products') ?></strong> + <strong><?= $block->escapeHtml(__('New Products')) ?></strong> </div> <div class="block-content"> <?php $suffix = $block->getNameInLayout(); ?> - <ol class="product-items" id="widget-new-products-<?= /* @escapeNotVerified */ $suffix ?>"> - <?php foreach ($_products->getItems() as $_product): ?> + <ol class="product-items" id="widget-new-products-<?= $block->escapeHtmlAttr($suffix) ?>"> + <?php foreach ($_products->getItems() as $_product) :?> <li class="product-item"> <div class="product-item-info"> - <a class="product-item-photo" href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" - title="<?= /* @escapeNotVerified */ $block->stripTags($_product->getName(), null, true) ?>"> + <a class="product-item-photo" href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + title="<?= /* @noEscape */ $block->stripTags($_product->getName(), null, true) ?>"> <?= $block->getImage($_product, 'side_column_widget_product_thumbnail')->toHtml() ?> </a> <div class="product-item-details"> <strong class="product-item-name"> - <a href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" - title="<?= /* @escapeNotVerified */ $block->stripTags($_product->getName(), null, true) ?>)" class="product-item-link"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Output')->productAttribute($_product, $_product->getName(), 'name') ?> + <a href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + title="<?= /* @noEscape */ $block->stripTags($_product->getName(), null, true) ?>)" + class="product-item-link"> + <?= /* @noEscape */ $this->helper(Magento\Catalog\Helper\Output::class)->productAttribute($_product, $_product->getName(), 'name') ?> </a> </strong> - <?= /* @escapeNotVerified */ $block->getProductPriceHtml($_product, '-widget-new-' . $suffix) ?> + <?= $block->getProductPriceHtml($_product, '-widget-new-' . $suffix) ?> <div class="product-item-actions"> <div class="actions-primary"> - <?php if ($_product->isSaleable()): ?> - <?php if (!$_product->getTypeInstance()->isPossibleBuyFromList($_product)): ?> - <button type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>" + <?php if ($_product->isSaleable()) :?> + <?php if (!$_product->getTypeInstance()->isPossibleBuyFromList($_product)) :?> + <button type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>" class="action tocart primary" - data-mage-init='{"redirectUrl":{"url":"<?= /* @escapeNotVerified */ $block->getAddToCartUrl($_product) ?>"}}'> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-mage-init='{"redirectUrl":{"url":"<?= $block->escapeUrl($block->getAddToCartUrl($_product)) ?>"}}'> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> - <?php else: ?> + <?php else :?> <?php - $postDataHelper = $this->helper('Magento\Framework\Data\Helper\PostHelper'); - $postData = $postDataHelper->getPostData($block->getAddToCartUrl($_product), ['product' => $_product->getEntityId()]); - ?> - <button type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>" + $postDataHelper = $this->helper(Magento\Framework\Data\Helper\PostHelper::class); + $postData = $postDataHelper->getPostData($block->escapeUrl($block->getAddToCartUrl($_product)), ['product' => $_product->getEntityId()]); + ?> + <button type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>" class="action tocart primary" - data-post='<?= /* @escapeNotVerified */ $postData ?>'> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-post='<?= /* @noEscape */ $postData ?>'> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> <?php endif; ?> - <?php else: ?> - <?php if ($_product->getIsSalable()): ?> - <div class="stock available" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('In stock') ?></span> + <?php else :?> + <?php if ($_product->getIsSalable()) :?> + <div class="stock available" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('In stock')) ?></span> </div> - <?php else: ?> - <div class="stock unavailable" title="<?= /* @escapeNotVerified */ __('Availability') ?>"> - <span><?= /* @escapeNotVerified */ __('Out of stock') ?></span> + <?php else :?> + <div class="stock unavailable" title="<?= $block->escapeHtmlAttr(__('Availability')) ?>"> + <span><?= $block->escapeHtml(__('Out of stock')) ?></span> </div> <?php endif; ?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_images_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_images_list.phtml index 2c40f9f7d63dc..8a776adc95018 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_images_list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_images_list.phtml @@ -3,22 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if (($_products = $block->getProductCollection()) && $_products->getSize()): ?> +<?php if (($_products = $block->getProductCollection()) && $_products->getSize()) :?> <div class="block widget block-new-products-images"> <div class="block-title"> - <strong><?= /* @escapeNotVerified */ __('New Products') ?></strong> + <strong><?= $block->escapeHtml(__('New Products')) ?></strong> </div> <div class="block-content"> <?php $suffix = $block->getNameInLayout(); ?> - <ol id="widget-new-products-<?= /* @escapeNotVerified */ $suffix ?>" class="product-items product-items-images"> - <?php foreach ($_products->getItems() as $_product): ?> + <ol id="widget-new-products-<?= $block->escapeHtmlAttr($suffix) ?>" + class="product-items product-items-images"> + <?php foreach ($_products->getItems() as $_product) :?> <li class="product-item"> - <a class="product-item-photo" href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" - title="<?= /* @escapeNotVerified */ $block->stripTags($_product->getName(), null, true) ?>"> + <a class="product-item-photo" href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + title="<?= /* @noEscape */ $block->stripTags($_product->getName(), null, true) ?>"> <?php /* new_products_images_only_widget */ ?> <?= $block->getImage($_product, 'new_products_images_only_widget')->toHtml() ?> </a> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_names_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_names_list.phtml index c0fb12df91137..371d4df7c0206 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_names_list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_names_list.phtml @@ -4,24 +4,26 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> -<?php if (($_products = $block->getProductCollection()) && $_products->getSize()): ?> +<?php if (($_products = $block->getProductCollection()) && $_products->getSize()) :?> <div class="block widget block-new-products-names"> <div class="block-title"> - <strong><?= /* @escapeNotVerified */ __('New Products') ?></strong> + <strong><?= $block->escapeHtml(__('New Products')) ?></strong> </div> <div class="block-content"> <?php $suffix = $block->getNameInLayout(); ?> - <ol id="widget-new-products-<?= /* @escapeNotVerified */ $suffix ?>" class="product-items product-items-names"> - <?php foreach ($_products->getItems() as $_product): ?> + <ol id="widget-new-products-<?= $block->escapeHtmlAttr($suffix) ?>" + class="product-items product-items-names"> + <?php foreach ($_products->getItems() as $_product) :?> <li class="product-item"> <strong class="product-item-name"> - <a href="<?= /* @escapeNotVerified */ $_product->getProductUrl() ?>" - title="<?= /* @escapeNotVerified */ $block->stripTags($_product->getName(), null, true) ?>)" + <a href="<?= $block->escapeUrl($_product->getProductUrl()) ?>" + title="<?= /* @noEscape */ $block->stripTags($_product->getName(), null, true) ?>)" class="product-item-link"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Catalog\Helper\Output')->productAttribute($_product, $_product->getName(), 'name') ?> + <?= /* @noEscape */ $this->helper( + Magento\Catalog\Helper\Output::class + )->productAttribute($_product, $_product->getName(), 'name') ?> </a> </strong> </li> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml index 93542c4c9095c..5108c488aec19 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,6 +10,10 @@ * * @var $block \Magento\Catalog\Block\Product\Widget\NewWidget */ + +// phpcs:disable Magento2.Files.LineLength.MaxExceeded +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis + if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())) { $type = 'widget-new-grid'; @@ -30,84 +31,93 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()-> } ?> -<?php if ($exist):?> - <div class="block widget block-new-products <?= /* @escapeNotVerified */ $mode ?>"> +<?php if ($exist) :?> + <div class="block widget block-new-products <?= /* @noEscape */ $mode ?>"> <div class="block-title"> - <strong role="heading" aria-level="2"><?= /* @escapeNotVerified */ $title ?></strong> + <strong role="heading" aria-level="2"><?= $block->escapeHtml($title) ?></strong> </div> <div class="block-content"> - <?= /* @escapeNotVerified */ '<!-- ' . $image . '-->' ?> - <div class="products-<?= /* @escapeNotVerified */ $mode ?> <?= /* @escapeNotVerified */ $mode ?>"> - <ol class="product-items <?= /* @escapeNotVerified */ $type ?>"> - <?php foreach ($items as $_item): ?> + <?= /* @noEscape */ '<!-- ' . $image . '-->' ?> + <div class="products-<?= /* @noEscape */ $mode ?> <?= /* @noEscape */ $mode ?>"> + <ol class="product-items <?= /* @noEscape */ $type ?>"> + <?php foreach ($items as $_item) :?> <li class="product-item"> <div class="product-item-info"> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" class="product-item-photo"> + <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" + class="product-item-photo"> <?= $block->getImage($_item, $image)->toHtml() ?> </a> <div class="product-item-details"> <strong class="product-item-name"> <a title="<?= $block->escapeHtml($_item->getName()) ?>" - href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" + href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-link"> <?= $block->escapeHtml($_item->getName()) ?> </a> </strong> - <?php - echo $block->getProductPriceHtml($_item, $type); - ?> + <?= $block->getProductPriceHtml($_item, $type); ?> - <?php if ($templateType): ?> + <?php if ($templateType) :?> <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> <?php endif; ?> - <?php if ($showWishlist || $showCompare || $showCart): ?> + <?php if ($showWishlist || $showCompare || $showCart) :?> <div class="product-item-actions"> - <?php if ($showCart): ?> + <?php if ($showCart) :?> <div class="actions-primary"> - <?php if ($_item->isSaleable()): ?> - <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_item)): ?> + <?php if ($_item->isSaleable()) :?> + <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_item)) :?> <button class="action tocart primary" - data-mage-init='{"redirectUrl":{"url":"<?= /* @escapeNotVerified */ $block->getAddToCartUrl($_item) ?>"}}' - type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-mage-init='{"redirectUrl":{"url":"<?= $block->escapeUrl($block->getAddToCartUrl($_item)) ?>"}}' + type="button" + title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> - <?php else: ?> + <?php else :?> <?php - $postDataHelper = $this->helper('Magento\Framework\Data\Helper\PostHelper'); - $postData = $postDataHelper->getPostData($block->getAddToCartUrl($_item), ['product' => $_item->getEntityId()]) + $postDataHelper = $this->helper(Magento\Framework\Data\Helper\PostHelper::class); + $postData = $postDataHelper->getPostData( + $block->escapeUrl($block->getAddToCartUrl($_item)), + ['product' => (int) $_item->getEntityId()] + ) ?> <button class="action tocart primary" - data-post='<?= /* @escapeNotVerified */ $postData ?>' - type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-post='<?= /* @noEscape */ $postData ?>' + type="button" + title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> <?php endif; ?> - <?php else: ?> - <?php if ($_item->getIsSalable()): ?> - <div class="stock available"><span><?= /* @escapeNotVerified */ __('In stock') ?></span></div> - <?php else: ?> - <div class="stock unavailable"><span><?= /* @escapeNotVerified */ __('Out of stock') ?></span></div> + <?php else :?> + <?php if ($_item->getIsSalable()) :?> + <div class="stock available"> + <span><?= $block->escapeHtml(__('In stock')) ?></span> + </div> + <?php else :?> + <div class="stock unavailable"> + <span><?= $block->escapeHtml(__('Out of stock')) ?></span> + </div> <?php endif; ?> <?php endif; ?> </div> <?php endif; ?> - <?php if ($showWishlist || $showCompare): ?> + <?php if ($showWishlist || $showCompare) :?> <div class="actions-secondary" data-role="add-to-links"> - <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow() && $showWishlist): ?> + <?php if ($this->helper(Magento\Wishlist\Helper\Data::class)->isAllow() && $showWishlist) :?> <a href="#" - data-post='<?= /* @escapeNotVerified */ $block->getAddToWishlistParams($_item) ?>' - class="action towishlist" data-action="add-to-wishlist" - title="<?= /* @escapeNotVerified */ __('Add to Wish List') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Wish List') ?></span> + data-post='<?= /* @noEscape */ $block->getAddToWishlistParams($_item) ?>' + class="action towishlist" + data-action="add-to-wishlist" + title="<?= $block->escapeHtmlAttr(__('Add to Wish List')) ?>"> + <span><?= $block->escapeHtml(__('Add to Wish List')) ?></span> </a> <?php endif; ?> - <?php if ($block->getAddToCompareUrl() && $showCompare): ?> - <?php $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare');?> + <?php if ($block->getAddToCompareUrl() && $showCompare) :?> + <?php $compareHelper = $this->helper(Magento\Catalog\Helper\Product\Compare::class);?> <a href="#" class="action tocompare" - data-post='<?= /* @escapeNotVerified */ $compareHelper->getPostDataParams($_item) ?>' - title="<?= /* @escapeNotVerified */ __('Add to Compare') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Compare') ?></span> + data-post='<?= /* @noEscape */ $compareHelper->getPostDataParams($_item) ?>' + title="<?= $block->escapeHtmlAttr(__('Add to Compare')) ?>"> + <span><?= $block->escapeHtml(__('Add to Compare')) ?></span> </a> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml index ad75a3a6f0743..378cd49493a6e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml @@ -3,11 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php + +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +// phpcs:disable Magento2.Files.LineLength.MaxExceeded + /** * Template for displaying new products widget * @@ -21,7 +22,8 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()-> $image = 'new_products_content_widget_list'; $title = __('New Products'); $items = $block->getProductCollection()->getItems(); - $_helper = $this->helper('Magento\Catalog\Helper\Output'); + /** @var Magento\Catalog\Helper\Output $_helper */ + $_helper = $this->helper(Magento\Catalog\Helper\Output::class); $showWishlist = true; $showCompare = true; @@ -31,94 +33,102 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()-> } ?> -<?php if ($exist):?> - <div class="block widget block-new-products <?= /* @escapeNotVerified */ $mode ?>"> +<?php if ($exist) :?> + <div class="block widget block-new-products <?= /* @noEscape */ $mode ?>"> <div class="block-title"> - <strong role="heading" aria-level="2"><?= /* @escapeNotVerified */ $title ?></strong> + <strong role="heading" aria-level="2"><?= $block->escapeHtml($title) ?></strong> </div> <div class="block-content"> - <?= /* @escapeNotVerified */ '<!-- ' . $image . '-->' ?> - <div class="products-<?= /* @escapeNotVerified */ $mode ?> <?= /* @escapeNotVerified */ $mode ?>"> - <ol class="product-items <?= /* @escapeNotVerified */ $type ?>"> - <?php foreach ($items as $_item): ?> + <?= /* @noEscape */ '<!-- ' . $image . '-->' ?> + <div class="products-<?= /* @noEscape */ $mode ?> <?= /* @noEscape */ $mode ?>"> + <ol class="product-items <?= /* @noEscape */ $type ?>"> + <?php foreach ($items as $_item) :?> <li class="product-item"> <div class="product-item-info"> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" class="product-item-photo"> + <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" + class="product-item-photo"> <?= $block->getImage($_item, $image)->toHtml() ?> </a> <div class="product-item-details"> <strong class="product-item-name"> - <a title="<?= $block->escapeHtml($_item->getName()) ?>" - href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" + <a title="<?= $block->escapeHtmlAttr($_item->getName()) ?>" + href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-link"> <?= $block->escapeHtml($_item->getName()) ?> </a> </strong> <?= $block->getProductPriceHtml($_item, $type) ?> - <?php if ($templateType): ?> + <?php if ($templateType) :?> <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> <?php endif; ?> - <?php if ($showWishlist || $showCompare || $showCart): ?> + <?php if ($showWishlist || $showCompare || $showCart) :?> <div class="product-item-actions"> - <?php if ($showCart): ?> + <?php if ($showCart) :?> <div class="actions-primary"> - <?php if ($_item->isSaleable()): ?> - <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_item)): ?> + <?php if ($_item->isSaleable()) :?> + <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_item) + ) :?> <button class="action tocart primary" - data-mage-init='{"redirectUrl":{"url":"<?= /* @escapeNotVerified */ $block->getAddToCartUrl($_item) ?>"}}' - type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-mage-init='{"redirectUrl":{"url":"<?= $block->escapeUrl($block->getAddToCartUrl($_item)) ?>"}}' + type="button" + title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> - <?php else: ?> + <?php else :?> <?php - $postDataHelper = $this->helper('Magento\Framework\Data\Helper\PostHelper'); + $postDataHelper = $this->helper(Magento\Framework\Data\Helper\PostHelper::class); $postData = $postDataHelper->getPostData($block->getAddToCartUrl($_item), ['product' => $_item->getEntityId()]) ?> <button class="action tocart primary" - data-post='<?= /* @escapeNotVerified */ $postData ?>' - type="button" title="<?= /* @escapeNotVerified */ __('Add to Cart') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + data-post='<?= /* @noEscape */ $postData ?>' + type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> <?php endif; ?> - <?php else: ?> - <?php if ($_item->getIsSalable()): ?> - <div class="stock available"><span><?= /* @escapeNotVerified */ __('In stock') ?></span></div> - <?php else: ?> - <div class="stock unavailable"><span><?= /* @escapeNotVerified */ __('Out of stock') ?></span></div> + <?php else :?> + <?php if ($_item->getIsSalable()) :?> + <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div> + <?php else :?> + <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div> <?php endif; ?> <?php endif; ?> </div> <?php endif; ?> - <?php if ($showWishlist || $showCompare): ?> + <?php if ($showWishlist || $showCompare) :?> <div class="actions-secondary" data-role="add-to-links"> - <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow() && $showWishlist): ?> + <?php if ($this->helper(Magento\Wishlist\Helper\Data::class)->isAllow() && $showWishlist) :?> <a href="#" - data-post='<?= /* @escapeNotVerified */ $block->getAddToWishlistParams($_item) ?>' + data-post='<?= /* @noEscape */ $block->getAddToWishlistParams($_item) ?>' class="action towishlist" data-action="add-to-wishlist" - title="<?= /* @escapeNotVerified */ __('Add to Wish List') ?>"> - <span><?= /* @escapeNotVerified */ __('Add to Wish List') ?></span> + title="<?= $block->escapeHtmlAttr(__('Add to Wish List')) ?>"> + <span><?= $block->escapeHtml(__('Add to Wish List')) ?></span> </a> <?php endif; ?> - <?php if ($block->getAddToCompareUrl() && $showCompare): ?> - <?php $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare'); ?> + <?php if ($block->getAddToCompareUrl() && $showCompare) :?> + <?php $compareHelper = $this->helper(Magento\Catalog\Helper\Product\Compare::class); ?> <a href="#" class="action tocompare" - title="<?= /* @escapeNotVerified */ __('Add to Compare') ?>" - data-post='<?= /* @escapeNotVerified */ $compareHelper->getPostDataParams($_item) ?>'> - <span><?= /* @escapeNotVerified */ __('Add to Compare') ?></span> + title="<?= $block->escapeHtmlAttr(__('Add to Compare')) ?>" + data-post='<?= /* @noEscape */ $compareHelper->getPostDataParams($_item) ?>' + > + <span><?= $block->escapeHtml(__('Add to Compare')) ?></span> </a> <?php endif; ?> </div> <?php endif; ?> </div> <?php endif; ?> - <?php if ($description):?> + <?php if ($description) :?> <div class="product-item-description"> - <?= /* @escapeNotVerified */ $_helper->productAttribute($_item, $_item->getShortDescription(), 'short_description') ?> - <a title="<?= $block->escapeHtml($_item->getName()) ?>" - href="<?= /* @escapeNotVerified */ $block->getProductUrl($_item) ?>" - class="action more"><?= /* @escapeNotVerified */ __('Learn More') ?></a> + <?= /* @noEscape */ $_helper->productAttribute( + $_item, + $_item->getShortDescription(), + 'short_description' + ) ?> + <a title="<?= $block->escapeHtmlAttr($_item->getName()) ?>" + href="<?= $block->escapeUrl($block->getProductUrl($_item))?>" + class="action more"><?= $block->escapeHtml(__('Learn More')) ?></a> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/grid.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/grid.phtml index 578630a11e930..d4db174dbe5e7 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/grid.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/grid.phtml @@ -5,12 +5,13 @@ */ ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?= /* @escapeNotVerified */ $block->renderApp([ +<?php /* @noEscape */ echo $block->renderApp([ 'widget_columns' => [ 'displayMode' => 'grid' ], diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/list.phtml index 3770c330ad73e..e03ac9ca692cc 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/list.phtml @@ -5,12 +5,13 @@ */ ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?= /* @escapeNotVerified */ $block->renderApp([ +<?php /* @noEscape */ echo $block->renderApp([ 'widget_columns' => [ 'displayMode' => 'list' ], diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml index 1c4ad3105a2b5..a42b46a5238f9 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/viewed/sidebar.phtml @@ -3,16 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - - //@codingStandardsIgnoreFile ?> <?php -/** - * @var $block \Magento\Ui\Block\Wrapper - */ +// phpcs:disable Magento2.PHP.ShortEchoSyntax.ShortEchoTag + +/** @var $block \Magento\Ui\Block\Wrapper */ ?> -<?php /* @escapeNotVerified */ echo $block->renderApp( +<?php /* @noEscape */ echo $block->renderApp( [ 'listing' => [ 'displayMode' => 'grid' diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index bcb7c668657d3..d74105fe531e4 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -8,7 +8,7 @@ define([ 'mage/translate', 'underscore', 'Magento_Catalog/js/product/view/product-ids-resolver', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($, $t, _, idsResolver) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/gallery.js b/app/code/Magento/Catalog/view/frontend/web/js/gallery.js index df503cb42287b..f6be6fd58ca25 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/gallery.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/gallery.js @@ -9,7 +9,7 @@ if (typeof define === 'function' && define.amd) { define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], factory); } else { factory(jQuery); diff --git a/app/code/Magento/Catalog/view/frontend/web/js/list.js b/app/code/Magento/Catalog/view/frontend/web/js/list.js index 8017aef2413a6..6b1fb7f86a97a 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/list.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/list.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js b/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js index b8b6ff65be2b4..6589f7eb0ba48 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js b/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js index 3e29e1ebd4d9c..da03643864442 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/remaining-characters.js @@ -6,7 +6,7 @@ define([ 'jquery', 'mage/translate', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($, $t) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js index 66df48c28bfab..5845b6d7afe38 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/translate' ], function ($) { 'use strict'; @@ -26,8 +26,8 @@ define([ * @private */ _create: function () { - $(this.options.selectAllLink).on('click', $.proxy(this._selectAllRelated, this)); - $(this.options.relatedCheckbox).on('click', $.proxy(this._addRelatedToProduct, this)); + $(this.options.selectAllLink, this.element).on('click', $.proxy(this._selectAllRelated, this)); + $(this.options.relatedCheckbox, this.element).on('click', $.proxy(this._addRelatedToProduct, this)); this._showRelatedProducts( this.element.find(this.options.elementsSelector), this.element.data('limit'), diff --git a/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js b/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js index 28e5daabdc3b2..da2526d5679c8 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/zoom.js b/app/code/Magento/Catalog/view/frontend/web/js/zoom.js index f6444223e2c54..1c68818ed9c4e 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/zoom.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/zoom.js @@ -8,7 +8,7 @@ */ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js b/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js index ad652b8ef36fe..ab1753e7b9ed3 100644 --- a/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js +++ b/app/code/Magento/Catalog/view/frontend/web/product/view/validation.js @@ -9,7 +9,7 @@ if (typeof define === 'function' && define.amd) { define([ 'jquery', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/validation/validation' ], factory); } else { diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json index afe3267b8e06b..d3f2a63b6b6e1 100644 --- a/app/code/Magento/CatalogAnalytics/composer.json +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-analytics", "description": "N/A", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-analytics": "100.3.*" @@ -20,5 +20,5 @@ "Magento\\CatalogAnalytics\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php new file mode 100644 index 0000000000000..7cdeb33e39db8 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/MediaGalleryTypeResolver.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model; + +use \Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; + +/** + * Resolver for Media Gallery type. + */ +class MediaGalleryTypeResolver implements TypeResolverInterface +{ + /** + * @inheritdoc + * + * @param array $data + * @return string + */ + public function resolveType(array $data) : string + { + // resolve type based on the data + if (isset($data['media_type']) && $data['media_type'] == 'image') { + return 'ProductImage'; + } + if (isset($data['media_type']) && $data['media_type'] == 'external-video') { + return 'ProductVideo'; + } + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php index cb392a7b2295d..535fe3a80cd25 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Api\Data\CategoryInterface; @@ -18,6 +19,7 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator; +use Magento\Store\Model\StoreManagerInterface; /** * Resolver for category objects the product is assigned to. @@ -56,25 +58,41 @@ class Categories implements ResolverInterface */ private $categoryHydrator; + /** + * @var ProductCategories + */ + private $productCategories; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner * @param CustomAttributesFlattener $customAttributesFlattener * @param ValueFactory $valueFactory * @param CategoryHydrator $categoryHydrator + * @param ProductCategories $productCategories + * @param StoreManagerInterface $storeManager */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, CustomAttributesFlattener $customAttributesFlattener, ValueFactory $valueFactory, - CategoryHydrator $categoryHydrator + CategoryHydrator $categoryHydrator, + ProductCategories $productCategories, + StoreManagerInterface $storeManager ) { $this->collection = $collectionFactory->create(); $this->attributesJoiner = $attributesJoiner; $this->customAttributesFlattener = $customAttributesFlattener; $this->valueFactory = $valueFactory; $this->categoryHydrator = $categoryHydrator; + $this->productCategories = $productCategories; + $this->storeManager = $storeManager; } /** @@ -90,39 +108,42 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value /** @var \Magento\Catalog\Model\Product $product */ $product = $value['model']; - $categoryIds = $product->getCategoryIds(); + $storeId = $this->storeManager->getStore()->getId(); + $categoryIds = $this->productCategories->getCategoryIdsByProduct((int)$product->getId(), (int)$storeId); $this->categoryIds = array_merge($this->categoryIds, $categoryIds); $that = $this; - return $this->valueFactory->create(function () use ($that, $categoryIds, $info) { - $categories = []; - if (empty($that->categoryIds)) { - return []; - } + return $this->valueFactory->create( + function () use ($that, $categoryIds, $info) { + $categories = []; + if (empty($that->categoryIds)) { + return []; + } - if (!$this->collection->isLoaded()) { - $that->attributesJoiner->join($info->fieldNodes[0], $this->collection); - $this->collection->addIdFilter($this->categoryIds); - } - /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */ - foreach ($this->collection as $item) { - if (in_array($item->getId(), $categoryIds)) { - // Try to extract all requested fields from the loaded collection data - $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true); - $categories[$item->getId()]['model'] = $item; - $requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]); - $extractedFields = array_keys($categories[$item->getId()]); - $foundFields = array_intersect($requestedFields, $extractedFields); - if (count($requestedFields) === count($foundFields)) { - continue; - } + if (!$this->collection->isLoaded()) { + $that->attributesJoiner->join($info->fieldNodes[0], $this->collection); + $this->collection->addIdFilter($this->categoryIds); + } + /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */ + foreach ($this->collection as $item) { + if (in_array($item->getId(), $categoryIds)) { + // Try to extract all requested fields from the loaded collection data + $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true); + $categories[$item->getId()]['model'] = $item; + $requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]); + $extractedFields = array_keys($categories[$item->getId()]); + $foundFields = array_intersect($requestedFields, $extractedFields); + if (count($requestedFields) === count($foundFields)) { + continue; + } - // If not all requested fields were extracted from the collection, start more complex extraction - $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item); + // If not all requested fields were extracted from the collection, start more complex extraction + $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item); + } } - } - return $categories; - }); + return $categories; + } + ); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CheckCategoryIsActive.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CheckCategoryIsActive.php new file mode 100644 index 0000000000000..16f816a967123 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CheckCategoryIsActive.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category; + +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; + +/** + * Check if category is active. + */ +class CheckCategoryIsActive +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var MetadataPool + */ + private $metadata; + + /** + * @param CollectionFactory $collectionFactory + * @param MetadataPool $metadata + */ + public function __construct( + CollectionFactory $collectionFactory, + MetadataPool $metadata + ) { + $this->collectionFactory = $collectionFactory; + $this->metadata = $metadata; + } + + /** + * Check if category is active. + * + * @param int $categoryId + * @throws GraphQlNoSuchEntityException + */ + public function execute(int $categoryId): void + { + $collection = $this->collectionFactory->create(); + $collection->addAttributeToFilter(CategoryInterface::KEY_IS_ACTIVE, ['eq' => 1]) + ->getSelect() + ->where( + $collection->getSelect() + ->getConnection() + ->quoteIdentifier( + 'e.' . + $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() + ) . ' = ?', + $categoryId + ); + + if ($collection->count() === 0) { + throw new GraphQlNoSuchEntityException(__('Category doesn\'t exist')); + } + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php index 7a41f8fc94e74..e0580213ddea7 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php @@ -66,6 +66,12 @@ public function resolve( ] ]; $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args); + if ($args['currentPage'] < 1) { + throw new GraphQlInputException(__('currentPage value must be greater than 0.')); + } + if ($args['pageSize'] < 1) { + throw new GraphQlInputException(__('pageSize value must be greater than 0.')); + } $searchCriteria->setCurrentPage($args['currentPage']); $searchCriteria->setPageSize($args['pageSize']); $searchResult = $this->filterQuery->getResult($searchCriteria, $info); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php index cb5553bb03701..44ea0222ba59d 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/SortFields.php @@ -7,6 +7,8 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Category; +use Magento\Catalog\Model\Category\Attribute\Source\Sortby; +use Magento\Catalog\Model\Config; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -17,32 +19,24 @@ class SortFields implements ResolverInterface { /** - * @var \Magento\Catalog\Model\Config + * @var Config */ private $catalogConfig; - - /** - * @var \Magento\Store\Model\StoreManagerInterface - */ - private $storeManager; - + /** - * @var \Magento\Catalog\Model\Category\Attribute\Source\Sortby + * @var Sortby */ private $sortbyAttributeSource; /** - * @param \Magento\Catalog\Model\Config $catalogConfig - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @oaram \Magento\Catalog\Model\Category\Attribute\Source\Sortby $sortbyAttributeSource + * @param Config $catalogConfig + * @param Sortby $sortbyAttributeSource */ public function __construct( - \Magento\Catalog\Model\Config $catalogConfig, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Category\Attribute\Source\Sortby $sortbyAttributeSource + Config $catalogConfig, + Sortby $sortbyAttributeSource ) { $this->catalogConfig = $catalogConfig; - $this->storeManager = $storeManager; $this->sortbyAttributeSource = $sortbyAttributeSource; } @@ -52,6 +46,8 @@ public function __construct( public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { $sortFieldsOptions = $this->sortbyAttributeSource->getAllOptions(); + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + array_walk( $sortFieldsOptions, function (&$option) { @@ -59,10 +55,10 @@ function (&$option) { } ); $data = [ - 'default' => $this->catalogConfig->getProductListDefaultSortBy($this->storeManager->getStore()->getId()), + 'default' => $this->catalogConfig->getProductListDefaultSortBy($storeId), 'options' => $sortFieldsOptions, ]; - + return $data; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 1783a5cd9a7e5..89d3805383e1a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -7,6 +7,8 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\Catalog\Model\Category; +use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; @@ -34,16 +36,24 @@ class CategoryTree implements ResolverInterface */ private $extractDataFromCategoryTree; + /** + * @var CheckCategoryIsActive + */ + private $checkCategoryIsActive; + /** * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree + * @param CheckCategoryIsActive $checkCategoryIsActive */ public function __construct( \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree, - ExtractDataFromCategoryTree $extractDataFromCategoryTree + ExtractDataFromCategoryTree $extractDataFromCategoryTree, + CheckCategoryIsActive $checkCategoryIsActive ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; + $this->checkCategoryIsActive = $checkCategoryIsActive; } /** @@ -72,6 +82,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $rootCategoryId = $this->getCategoryId($args); + if ($rootCategoryId !== Category::TREE_ROOT_ID) { + $this->checkCategoryIsActive->execute($rootCategoryId); + } $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); if (empty($categoriesTree) || ($categoriesTree->count() == 0)) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php index 40aa54fd93873..86137990cc57d 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php @@ -7,14 +7,13 @@ namespace Magento\CatalogGraphQl\Model\Resolver; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CatalogGraphQl\Model\Resolver\Product\ProductFieldsSelector; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product as ProductDataProvider; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\GraphQl\Query\FieldTranslator; -use Magento\Framework\GraphQl\Query\Resolver\Value; use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; /** * @inheritdoc @@ -32,23 +31,23 @@ class Product implements ResolverInterface private $valueFactory; /** - * @var FieldTranslator + * @var ProductFieldsSelector */ - private $fieldTranslator; + private $productFieldsSelector; /** * @param ProductDataProvider $productDataProvider * @param ValueFactory $valueFactory - * @param FieldTranslator $fieldTranslator + * @param ProductFieldsSelector $productFieldsSelector */ public function __construct( ProductDataProvider $productDataProvider, ValueFactory $valueFactory, - FieldTranslator $fieldTranslator + ProductFieldsSelector $productFieldsSelector ) { $this->productDataProvider = $productDataProvider; $this->valueFactory = $valueFactory; - $this->fieldTranslator = $fieldTranslator; + $this->productFieldsSelector = $productFieldsSelector; } /** @@ -60,7 +59,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value throw new GraphQlInputException(__('No child sku found for product link.')); } $this->productDataProvider->addProductSku($value['sku']); - $fields = $this->getProductFields($info); + $fields = $this->productFieldsSelector->getProductFieldsFromInfo($info); $this->productDataProvider->addEavAttributes($fields); $result = function () use ($value) { @@ -86,34 +85,4 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return $this->valueFactory->create($result); } - - /** - * Return field names for all requested product fields. - * - * @param ResolveInfo $info - * @return string[] - */ - private function getProductFields(ResolveInfo $info) : array - { - $fieldNames = []; - foreach ($info->fieldNodes as $node) { - if ($node->name->value !== 'product') { - continue; - } - foreach ($node->selectionSet->selections as $selectionNode) { - if ($selectionNode->kind === 'InlineFragment') { - foreach ($selectionNode->selectionSet->selections as $inlineSelection) { - if ($inlineSelection->kind === 'InlineFragment') { - continue; - } - $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); - } - continue; - } - $fieldNames[] = $this->fieldTranslator->translate($selectionNode->name->value); - } - } - - return $fieldNames; - } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php new file mode 100644 index 0000000000000..810de0f1f4b57 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; + +/** + * @inheritdoc + * + * Format a product's media gallery information to conform to GraphQL schema representation + */ +class MediaGallery implements ResolverInterface +{ + /** + * @inheritdoc + * + * Format product's media gallery entry data to conform to GraphQL schema + * + * @param \Magento\Framework\GraphQl\Config\Element\Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @throws \Exception + * @return array + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + /** @var ProductInterface $product */ + $product = $value['model']; + + $mediaGalleryEntries = []; + foreach ($product->getMediaGalleryEntries() ?? [] as $key => $entry) { + $mediaGalleryEntries[$key] = $entry->getData(); + $mediaGalleryEntries[$key]['model'] = $product; + if ($entry->getExtensionAttributes() && $entry->getExtensionAttributes()->getVideoContent()) { + $mediaGalleryEntries[$key]['video_content'] + = $entry->getExtensionAttributes()->getVideoContent()->getData(); + } + } + return $mediaGalleryEntries; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php similarity index 69% rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php index f971e35742628..4ec76fe59ca88 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Label.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Label.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\CatalogGraphQl\Model\Resolver\Product\ProductImage; +namespace Magento\CatalogGraphQl\Model\Resolver\Product\MediaGallery; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product as ProductResourceModel; @@ -13,10 +13,10 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Api\Data\StoreInterface; /** - * Returns product's image label + * Return media label */ class Label implements ResolverInterface { @@ -25,21 +25,13 @@ class Label implements ResolverInterface */ private $productResource; - /** - * @var StoreManagerInterface - */ - private $storeManager; - /** * @param ProductResourceModel $productResource - * @param StoreManagerInterface $storeManager */ public function __construct( - ProductResourceModel $productResource, - StoreManagerInterface $storeManager + ProductResourceModel $productResource ) { $this->productResource = $productResource; - $this->storeManager = $storeManager; } /** @@ -52,8 +44,9 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['image_type'])) { - throw new LocalizedException(__('"image_type" value should be specified')); + + if (isset($value['label'])) { + return $value['label']; } if (!isset($value['model'])) { @@ -62,18 +55,17 @@ public function resolve( /** @var Product $product */ $product = $value['model']; - $imageType = $value['image_type']; - $imagePath = $product->getData($imageType); $productId = (int)$product->getEntityId(); - - // null if image is not set - if (null === $imagePath) { - return $this->getAttributeValue($productId, 'name'); + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); + if (!isset($value['image_type'])) { + return $this->getAttributeValue($productId, 'name', $storeId); } - - $imageLabel = $this->getAttributeValue($productId, $imageType . '_label'); - if (null === $imageLabel) { - $imageLabel = $this->getAttributeValue($productId, 'name'); + $imageType = $value['image_type']; + $imageLabel = $this->getAttributeValue($productId, $imageType . '_label', $storeId); + if ($imageLabel == null) { + $imageLabel = $this->getAttributeValue($productId, 'name', $storeId); } return $imageLabel; @@ -84,12 +76,11 @@ public function resolve( * * @param int $productId * @param string $attributeCode + * @param int $storeId * @return null|string Null if attribute value is not exists */ - private function getAttributeValue(int $productId, string $attributeCode): ?string + private function getAttributeValue(int $productId, string $attributeCode, int $storeId): ?string { - $storeId = $this->storeManager->getStore()->getId(); - $value = $this->productResource->getAttributeRawValue($productId, $attributeCode, $storeId); return is_array($value) && empty($value) ? null : $value; } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php similarity index 77% rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php index 23a8c2d15c09e..eaab159cddae6 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGallery/Url.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\CatalogGraphQl\Model\Resolver\Product\ProductImage; +namespace Magento\CatalogGraphQl\Model\Resolver\Product\MediaGallery; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\ImageFactory; @@ -16,7 +16,7 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; /** - * Returns product's image url + * Returns media url */ class Url implements ResolverInterface { @@ -51,7 +51,7 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['image_type'])) { + if (!isset($value['image_type']) && !isset($value['file'])) { throw new LocalizedException(__('"image_type" value should be specified')); } @@ -61,9 +61,17 @@ public function resolve( /** @var Product $product */ $product = $value['model']; - $imagePath = $product->getData($value['image_type']); - - return $this->getImageUrl($value['image_type'], $imagePath); + if (isset($value['image_type'])) { + $imagePath = $product->getData($value['image_type']); + return $this->getImageUrl($value['image_type'], $imagePath); + } + if (isset($value['file'])) { + $image = $this->productImageFactory->create(); + $image->setDestinationSubdir('image')->setBaseFile($value['file']); + $imageUrl = $image->getUrl(); + return $imageUrl; + } + return []; } /** diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php index c8f167da583d3..8843ad02320c6 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/MediaGalleryEntries.php @@ -18,6 +18,7 @@ * @inheritdoc * * Format a product's media gallery information to conform to GraphQL schema representation + * @deprecated 100.3.3 */ class MediaGalleryEntries implements ResolverInterface { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php index 55d930101fb60..c542fc26495f7 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Price.php @@ -7,43 +7,35 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Product; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Model\Product; use Magento\Catalog\Pricing\Price\FinalPrice; use Magento\Catalog\Pricing\Price\RegularPrice; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Pricing\Adjustment\AdjustmentInterface; use Magento\Framework\Pricing\Amount\AmountInterface; use Magento\Framework\Pricing\PriceInfo\Factory as PriceInfoFactory; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Api\Data\StoreInterface; /** * Format a product's price information to conform to GraphQL schema representation */ class Price implements ResolverInterface { - /** - * @var StoreManagerInterface - */ - private $storeManager; - /** * @var PriceInfoFactory */ private $priceInfoFactory; /** - * @param StoreManagerInterface $storeManager * @param PriceInfoFactory $priceInfoFactory */ public function __construct( - StoreManagerInterface $storeManager, PriceInfoFactory $priceInfoFactory ) { - $this->storeManager = $storeManager; $this->priceInfoFactory = $priceInfoFactory; } @@ -80,11 +72,20 @@ public function resolve( $minimalPriceAmount = $finalPrice->getMinimalPrice(); $maximalPriceAmount = $finalPrice->getMaximalPrice(); $regularPriceAmount = $priceInfo->getPrice(RegularPrice::PRICE_CODE)->getAmount(); + $store = $context->getExtensionAttributes()->getStore(); $prices = [ - 'minimalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $minimalPriceAmount), - 'regularPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $regularPriceAmount), - 'maximalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $maximalPriceAmount) + 'minimalPrice' => $this->createAdjustmentsArray( + $priceInfo->getAdjustments(), + $minimalPriceAmount, + $store + ), + 'regularPrice' => $this->createAdjustmentsArray( + $priceInfo->getAdjustments(), + $regularPriceAmount, + $store + ), + 'maximalPrice' => $this->createAdjustmentsArray($priceInfo->getAdjustments(), $maximalPriceAmount, $store) ]; return $prices; @@ -95,13 +96,11 @@ public function resolve( * * @param AdjustmentInterface[] $adjustments * @param AmountInterface $amount + * @param StoreInterface $store * @return array */ - private function createAdjustmentsArray(array $adjustments, AmountInterface $amount) : array + private function createAdjustmentsArray(array $adjustments, AmountInterface $amount, StoreInterface $store) : array { - /** @var \Magento\Store\Model\Store $store */ - $store = $this->storeManager->getStore(); - $priceArray = [ 'amount' => [ 'value' => $amount->getValue(), diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php new file mode 100644 index 0000000000000..86984a29149ef --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductCategories.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver; +use Magento\Store\Model\Group; +use Magento\Store\Model\Store; + +/** + * Get all categories where product is visible + */ +class ProductCategories +{ + /** + * @var IndexScopeResolver + */ + private $tableResolver; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @param IndexScopeResolver $tableResolver + * @param ResourceConnection $resourceConnection + * @param DimensionFactory $dimensionFactory + */ + public function __construct( + IndexScopeResolver $tableResolver, + ResourceConnection $resourceConnection, + DimensionFactory $dimensionFactory + ) { + $this->tableResolver = $tableResolver; + $this->resourceConnection = $resourceConnection; + $this->dimensionFactory = $dimensionFactory; + } + + /** + * Get category ids for product + * + * @param int $productId + * @param int $storeId + * @return array + */ + public function getCategoryIdsByProduct(int $productId, int $storeId) + { + $connection = $this->resourceConnection->getConnection(); + $categoryProductTable = $this->getCatalogCategoryProductTableName($storeId); + $storeTable = $this->resourceConnection->getTableName(Store::ENTITY); + $storeGroupTable = $this->resourceConnection->getTableName(Group::ENTITY); + + $select = $connection->select() + ->from(['cat_index' => $categoryProductTable], ['category_id']) + ->joinInner(['store' => $storeTable], $connection->quoteInto('store.store_id = ?', $storeId), []) + ->joinInner( + ['store_group' => $storeGroupTable], + 'store.group_id = store_group.group_id AND cat_index.category_id != store_group.root_category_id', + [] + ) + ->where('product_id = ?', $productId); + + $categoryIds = $connection->fetchCol($select); + + return $categoryIds; + } + + /** + * Get catalog_category_product table name + * + * @param int $storeId + * @return string + */ + private function getCatalogCategoryProductTableName(int $storeId) + { + $dimension = $this->dimensionFactory->create(Store::ENTITY, (string)$storeId); + $tableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [$dimension] + ); + + return $tableName; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php new file mode 100644 index 0000000000000..9ddad4e6451fa --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Framework\GraphQl\Query\FieldTranslator; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Select Product Fields From Resolve Info + */ +class ProductFieldsSelector +{ + /** + * @var FieldTranslator + */ + private $fieldTranslator; + + /** + * @param FieldTranslator $fieldTranslator + */ + public function __construct(FieldTranslator $fieldTranslator) + { + $this->fieldTranslator = $fieldTranslator; + } + + /** + * Return field names for all requested product fields. + * + * @param ResolveInfo $info + * @param string $productNodeName + * @return string[] + */ + public function getProductFieldsFromInfo(ResolveInfo $info, string $productNodeName = 'product') : array + { + $fieldNames = []; + foreach ($info->fieldNodes as $node) { + if ($node->name->value !== $productNodeName) { + continue; + } + foreach ($node->selectionSet->selections as $selectionNode) { + if ($selectionNode->kind === 'InlineFragment') { + foreach ($selectionNode->selectionSet->selections as $inlineSelection) { + if ($inlineSelection->kind === 'InlineFragment') { + continue; + } + $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); + } + continue; + } + $fieldNames[] = $this->fieldTranslator->translate($selectionNode->name->value); + } + } + + return $fieldNames; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 24c5e664831e4..a75a9d2cf50a0 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -71,6 +71,12 @@ public function resolve( array $args = null ) { $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args); + if ($args['currentPage'] < 1) { + throw new GraphQlInputException(__('currentPage value must be greater than 0.')); + } + if ($args['pageSize'] < 1) { + throw new GraphQlInputException(__('pageSize value must be greater than 0.')); + } $searchCriteria->setCurrentPage($args['currentPage']); $searchCriteria->setPageSize($args['pageSize']); if (!isset($args['search']) && !isset($args['filter'])) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php index 7f1fd71942253..e5e0d1aea4285 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php @@ -89,6 +89,9 @@ public function getList( if (in_array('media_gallery_entries', $attributes)) { $collection->addMediaGalleryData(); } + if (in_array('media_gallery', $attributes)) { + $collection->addMediaGalleryData(); + } if (in_array('options', $attributes)) { $collection->addOptionsToResult(); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index f9b64f3a3c69b..bc40c664425ff 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -144,7 +144,7 @@ private function paginateList(SearchResult $searchResult, SearchCriteriaInterfac $offset = $length * ($searchCriteria->getCurrentPage() - 1); if ($searchCriteria->getPageSize()) { - $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize()) - 1; + $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize()); } else { $maxPages = 0; } diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index 9b1f396801d0c..11da4a26564e6 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-eav": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-catalog-inventory": "100.3.*", @@ -29,5 +29,5 @@ "Magento\\CatalogGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index a5bd42860ded0..2292004f3cf01 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -80,4 +80,19 @@ </virtualType> <preference for="Magento\Framework\Search\Adapter\Mysql\Query\Builder\Match" type="Magento\CatalogGraphQl\Model\Search\Adapter\Mysql\Query\Builder\Match" /> + <type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> + <arguments> + <argument name="extendedConfigData" xsi:type="array"> + <item name="product_url_suffix" xsi:type="string">catalog/seo/product_url_suffix</item> + <item name="category_url_suffix" xsi:type="string">catalog/seo/category_url_suffix</item> + <item name="title_separator" xsi:type="string">catalog/seo/title_separator</item> + <item name="list_mode" xsi:type="string">catalog/frontend/list_mode</item> + <item name="grid_per_page_values" xsi:type="string">catalog/frontend/grid_per_page_values</item> + <item name="list_per_page_values" xsi:type="string">catalog/frontend/list_per_page_values</item> + <item name="grid_per_page" xsi:type="string">catalog/frontend/grid_per_page</item> + <item name="list_per_page" xsi:type="string">catalog/frontend/list_per_page</item> + <item name="catalog_default_sort_by" xsi:type="string">catalog/frontend/default_sort_by</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index ae83272163127..ea56faf94408e 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -9,22 +9,22 @@ type Query { currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): Products - @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") + @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") category ( - id: Int @doc(description: "Id of the category") + id: Int @doc(description: "Id of the category.") ): CategoryTree - @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity") + @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity") } type Price @doc(description: "The Price object defines the price of a product as well as any tax-related adjustments.") { - amount: Money @doc(description: "The price of a product plus a three-letter currency code") - adjustments: [PriceAdjustment] @doc(description: "An array that provides information about tax, weee, or weee_tax adjustments") + amount: Money @doc(description: "The price of a product plus a three-letter currency code.") + adjustments: [PriceAdjustment] @doc(description: "An array that provides information about tax, weee, or weee_tax adjustments.") } type PriceAdjustment @doc(description: "The PricedAdjustment object defines the amount of money to apply as an adjustment, the type of adjustment to apply, and whether the item is included or excluded from the adjustment.") { - amount: Money @doc(description: "The amount of the price adjustment and its currency code") - code: PriceAdjustmentCodesEnum @doc(description: "Indicates whether the adjustment involves tax, weee, or weee_tax") - description: PriceAdjustmentDescriptionEnum @doc(description: "Indicates whether the entity described by the code attribute is included or excluded from the adjustment") + amount: Money @doc(description: "The amount of the price adjustment and its currency code.") + code: PriceAdjustmentCodesEnum @doc(description: "Indicates whether the adjustment involves tax, weee, or weee_tax.") + description: PriceAdjustmentDescriptionEnum @doc(description: "Indicates whether the entity described by the code attribute is included or excluded from the adjustment.") } enum PriceAdjustmentCodesEnum @doc(description: "Note: This enumeration contains values defined in modules other than the Catalog module.") { @@ -51,341 +51,345 @@ type ProductLinks implements ProductLinksInterface @doc(description: "ProductLin } interface ProductLinksInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductLinkTypeResolverComposite") @doc(description:"ProductLinks contains information about linked products, including the link type and product type of each item.") { - sku: String @doc(description: "The identifier of the linked product") - link_type: String @doc(description: "One of related, associated, upsell, or crosssell") - linked_product_sku: String @doc(description: "The SKU of the linked product") - linked_product_type: String @doc(description: "The type of linked product (simple, virtual, bundle, downloadable, grouped, configurable)") - position: Int @doc(description: "The position within the list of product links") + sku: String @doc(description: "The identifier of the linked product.") + link_type: String @doc(description: "One of related, associated, upsell, or crosssell.") + linked_product_sku: String @doc(description: "The SKU of the linked product.") + linked_product_type: String @doc(description: "The type of linked product (simple, virtual, bundle, downloadable, grouped, configurable).") + position: Int @doc(description: "The position within the list of product links.") } type ProductTierPrices @doc(description: "The ProductTierPrices object defines a tier price, which is a quantity discount offered to a specific customer group.") { - customer_group_id: String @doc(description: "The ID of the customer group") - qty: Float @doc(description: "The number of items that must be purchased to qualify for tier pricing") - value: Float @doc(description: "The price of the fixed price item") - percentage_value: Float @doc(description: "The percentage discount of the item") - website_id: Float @doc(description: "The ID assigned to the website") + customer_group_id: String @doc(description: "The ID of the customer group.") + qty: Float @doc(description: "The number of items that must be purchased to qualify for tier pricing.") + value: Float @doc(description: "The price of the fixed price item.") + percentage_value: Float @doc(description: "The percentage discount of the item.") + website_id: Float @doc(description: "The ID assigned to the website.") } interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "The ProductInterface contains attributes that are common to all types of products. Note that descriptions may not be available for custom and EAV attributes.") { - id: Int @doc(description: "The ID number assigned to the product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId") + id: Int @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId") name: String @doc(description: "The product name. Customers use this name to identify the product.") - sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") + sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: ComplexTextValue @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute") short_description: ComplexTextValue @doc(description: "A short description of the product. Its use depends on the theme.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute") - special_price: Float @doc(description: "The discounted price of the product") - special_from_date: String @doc(description: "The beginning date that a product has a special price") - special_to_date: String @doc(description: "The end date that a product has a special price") - attribute_set_id: Int @doc(description: "The attribute set assigned to the product") - meta_title: String @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists") - meta_keyword: String @doc(description: "A comma-separated list of keywords that are visible only to search engines") - meta_description: String @doc(description: "A brief overview of the product for search results listings, maximum 255 characters") - image: ProductImage @doc(description: "The relative path to the main image on the product page") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") - small_image: ProductImage @doc(description: "The relative path to the small image, which is used on catalog pages") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") - thumbnail: ProductImage @doc(description: "The relative path to the product's thumbnail image") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") - new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") - new_to_date: String @doc(description: "The end date for new product listings") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") - tier_price: Float @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached") - options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page") - created_at: String @doc(description: "Timestamp indicating when the product was created") - updated_at: String @doc(description: "Timestamp indicating when the product was updated") - country_of_manufacture: String @doc(description: "The product's country of origin") - type_id: String @doc(description: "One of simple, virtual, bundle, downloadable, grouped, or configurable") - websites: [Website] @doc(description: "An array of websites in which the product is available") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Websites") - product_links: [ProductLinksInterface] @doc(description: "An array of ProductLinks objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductLinks") - media_gallery_entries: [MediaGalleryEntry] @doc(description: "An array of MediaGalleryEntry objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGalleryEntries") - tier_prices: [ProductTierPrices] @doc(description: "An array of ProductTierPrices objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\TierPrices") - price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price") - gift_message_available: String @doc(description: "Indicates whether a gift message is available") - manufacturer: Int @doc(description: "A number representing the product's manufacturer") - categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") - canonical_url: String @doc(description: "Canonical URL") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl") -} - -interface PhysicalProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "PhysicalProductInterface contains attributes specific to tangible products") { - weight: Float @doc(description: "The weight of the item, in units defined by the store") -} - -type CustomizableAreaOption implements CustomizableOptionInterface @doc(description: "CustomizableAreaOption contains information about a text area that is defined as part of a customizable option") { - value: CustomizableAreaValue @doc(description: "An object that defines a text area") - product_sku: String @doc(description: "The Stock Keeping Unit of the base product") -} - -type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the price and sku of a product whose page contains a customized text area") { - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option") -} - -type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation") { - children: [CategoryTree] @doc(description: "Child categories tree") @resolve(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") -} - -type CustomizableDateOption implements CustomizableOptionInterface @doc(description: "CustomizableDateOption contains information about a date picker that is defined as part of a customizable option") { + special_price: Float @doc(description: "The discounted price of the product.") + special_from_date: String @doc(description: "The beginning date that a product has a special price.") + special_to_date: String @doc(description: "The end date that a product has a special price.") + attribute_set_id: Int @doc(description: "The attribute set assigned to the product.") + meta_title: String @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists.") + meta_keyword: String @doc(description: "A comma-separated list of keywords that are visible only to search engines.") + meta_description: String @doc(description: "A brief overview of the product for search results listings, maximum 255 characters.") + image: ProductImage @doc(description: "The relative path to the main image on the product page.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") + small_image: ProductImage @doc(description: "The relative path to the small image, which is used on catalog pages.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") + thumbnail: ProductImage @doc(description: "The relative path to the product's thumbnail image.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage") + new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") + new_to_date: String @doc(description: "The end date for new product listings.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") + tier_price: Float @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached.") + options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page.") + created_at: String @doc(description: "Timestamp indicating when the product was created.") + updated_at: String @doc(description: "Timestamp indicating when the product was updated.") + country_of_manufacture: String @doc(description: "The product's country of origin.") + type_id: String @doc(description: "One of simple, virtual, bundle, downloadable, grouped, or configurable.") + websites: [Website] @doc(description: "An array of websites in which the product is available.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Websites") + product_links: [ProductLinksInterface] @doc(description: "An array of ProductLinks objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductLinks") + media_gallery_entries: [MediaGalleryEntry] @deprecated(reason: "Use product's `media_gallery` instead") @doc(description: "An array of MediaGalleryEntry objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGalleryEntries") + tier_prices: [ProductTierPrices] @doc(description: "An array of ProductTierPrices objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\TierPrices") + price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price") + gift_message_available: String @doc(description: "Indicates whether a gift message is available.") + manufacturer: Int @doc(description: "A number representing the product's manufacturer.") + categories: [CategoryInterface] @doc(description: "The categories assigned to a product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") + canonical_url: String @doc(description: "Canonical URL.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl") + media_gallery: [MediaGalleryInterface] @doc(description: "An array of Media Gallery objects.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery") +} + +interface PhysicalProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "PhysicalProductInterface contains attributes specific to tangible products.") { + weight: Float @doc(description: "The weight of the item, in units defined by the store.") +} + +type CustomizableAreaOption implements CustomizableOptionInterface @doc(description: "CustomizableAreaOption contains information about a text area that is defined as part of a customizable option.") { + value: CustomizableAreaValue @doc(description: "An object that defines a text area.") + product_sku: String @doc(description: "The Stock Keeping Unit of the base product.") +} + +type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the price and sku of a product whose page contains a customized text area.") { + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option.") +} + +type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation.") { + children: [CategoryTree] @doc(description: "Child categories tree.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") +} + +type CustomizableDateOption implements CustomizableOptionInterface @doc(description: "CustomizableDateOption contains information about a date picker that is defined as part of a customizable option.") { value: CustomizableDateValue @doc(description: "An object that defines a date field in a customizable option.") - product_sku: String @doc(description: "The Stock Keeping Unit of the base product") + product_sku: String @doc(description: "The Stock Keeping Unit of the base product.") } -type CustomizableDateValue @doc(description: "CustomizableDateValue defines the price and sku of a product whose page contains a customized date picker") { - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") +type CustomizableDateValue @doc(description: "CustomizableDateValue defines the price and sku of a product whose page contains a customized date picker.") { + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") } -type CustomizableDropDownOption implements CustomizableOptionInterface @doc(description: "CustomizableDropDownOption contains information about a drop down menu that is defined as part of a customizable option") { - value: [CustomizableDropDownValue] @doc(description: "An array that defines the set of options for a drop down menu") +type CustomizableDropDownOption implements CustomizableOptionInterface @doc(description: "CustomizableDropDownOption contains information about a drop down menu that is defined as part of a customizable option.") { + value: [CustomizableDropDownValue] @doc(description: "An array that defines the set of options for a drop down menu.") } -type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defines the price and sku of a product whose page contains a customized drop down menu") { - option_type_id: Int @doc(description: "The ID assigned to the value") - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - title: String @doc(description: "The display name for this option") - sort_order: Int @doc(description: "The order in which the option is displayed") +type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defines the price and sku of a product whose page contains a customized drop down menu.") { + option_type_id: Int @doc(description: "The ID assigned to the value.") + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + title: String @doc(description: "The display name for this option.") + sort_order: Int @doc(description: "The order in which the option is displayed.") } -type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipleOption contains information about a multiselect that is defined as part of a customizable option") { - value: [CustomizableMultipleValue] @doc(description: "An array that defines the set of options for a multiselect") +type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipleOption contains information about a multiselect that is defined as part of a customizable option.") { + value: [CustomizableMultipleValue] @doc(description: "An array that defines the set of options for a multiselect.") } -type CustomizableMultipleValue @doc(description: "CustomizableMultipleValue defines the price and sku of a product whose page contains a customized multiselect") { - option_type_id: Int @doc(description: "The ID assigned to the value") - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - title: String @doc(description: "The display name for this option") - sort_order: Int @doc(description: "The order in which the option is displayed") +type CustomizableMultipleValue @doc(description: "CustomizableMultipleValue defines the price and sku of a product whose page contains a customized multiselect.") { + option_type_id: Int @doc(description: "The ID assigned to the value.") + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + title: String @doc(description: "The display name for this option.") + sort_order: Int @doc(description: "The order in which the option is displayed.") } -type CustomizableFieldOption implements CustomizableOptionInterface @doc(description: "CustomizableFieldOption contains information about a text field that is defined as part of a customizable option") { - value: CustomizableFieldValue @doc(description: "An object that defines a text field") - product_sku: String @doc(description: "The Stock Keeping Unit of the base product") +type CustomizableFieldOption implements CustomizableOptionInterface @doc(description: "CustomizableFieldOption contains information about a text field that is defined as part of a customizable option.") { + value: CustomizableFieldValue @doc(description: "An object that defines a text field.") + product_sku: String @doc(description: "The Stock Keeping Unit of the base product.") } -type CustomizableFieldValue @doc(description: "CustomizableFieldValue defines the price and sku of a product whose page contains a customized text field") { - price: Float @doc(description: "The price of the custom value") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option") +type CustomizableFieldValue @doc(description: "CustomizableFieldValue defines the price and sku of a product whose page contains a customized text field.") { + price: Float @doc(description: "The price of the custom value.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option.") } -type CustomizableFileOption implements CustomizableOptionInterface @doc(description: "CustomizableFileOption contains information about a file picker that is defined as part of a customizable option") { - value: CustomizableFileValue @doc(description: "An object that defines a file value") - product_sku: String @doc(description: "The Stock Keeping Unit of the base product") +type CustomizableFileOption implements CustomizableOptionInterface @doc(description: "CustomizableFileOption contains information about a file picker that is defined as part of a customizable option.") { + value: CustomizableFileValue @doc(description: "An object that defines a file value.") + product_sku: String @doc(description: "The Stock Keeping Unit of the base product.") } -type CustomizableFileValue @doc(description: "CustomizableFileValue defines the price and sku of a product whose page contains a customized file picker") { - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - file_extension: String @doc(description: "The file extension to accept") - image_size_x: Int @doc(description: "The maximum width of an image") - image_size_y: Int @doc(description: "The maximum height of an image") +type CustomizableFileValue @doc(description: "CustomizableFileValue defines the price and sku of a product whose page contains a customized file picker.") { + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + file_extension: String @doc(description: "The file extension to accept.") + image_size_x: Int @doc(description: "The maximum width of an image.") + image_size_y: Int @doc(description: "The maximum height of an image.") } -type ProductImage @doc(description: "Product image information. Contains image relative path, URL and label") { - url: String @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage\\Url") - label: String @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage\\Label") +interface MediaGalleryInterface @doc(description: "Contains basic information about a product image or video.") @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\MediaGalleryTypeResolver") { + url: String @doc(description: "The URL of the product image or video.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery\\Url") + label: String @doc(description: "The label of the product image or video.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery\\Label") +} + +type ProductImage implements MediaGalleryInterface @doc(description: "Product image information. Contains the image URL and label.") { } interface CustomizableOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CustomizableOptionTypeResolver") @doc(description: "The CustomizableOptionInterface contains basic information about a customizable option. It can be implemented by several types of configurable options.") { - title: String @doc(description: "The display name for this option") - required: Boolean @doc(description: "Indicates whether the option is required") - sort_order: Int @doc(description: "The order in which the option is displayed") - option_id: Int @doc(description: "Option ID") + title: String @doc(description: "The display name for this option.") + required: Boolean @doc(description: "Indicates whether the option is required.") + sort_order: Int @doc(description: "The order in which the option is displayed.") + option_id: Int @doc(description: "Option ID.") } interface CustomizableProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "CustomizableProductInterface contains information about customizable product options.") { - options: [CustomizableOptionInterface] @doc(description: "An array of options for a customizable product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Options") -} - -interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CategoryInterfaceTypeResolver") @doc(description: "CategoryInterface contains the full set of attributes that can be returned in a category search") { - id: Int @doc(description: "An ID that uniquely identifies the category") - description: String @doc(description: "An optional description of the category") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryHtmlAttribute") - name: String @doc(description: "The display name of the category") - path: String @doc(description: "Category Path") - path_in_store: String @doc(description: "Category path in store") - url_key: String @doc(description: "The url key assigned to the category") - url_path: String @doc(description: "The url path assigned to the category") - position: Int @doc(description: "The position of the category relative to other categories at the same level in tree") - level: Int @doc(description: "Indicates the depth of the category within the tree") - created_at: String @doc(description: "Timestamp indicating when the category was created") - updated_at: String @doc(description: "Timestamp indicating when the category was updated") - product_count: Int @doc(description: "The number of products in the category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\ProductsCount") - default_sort_by: String @doc(description: "The attribute to use for sorting") + options: [CustomizableOptionInterface] @doc(description: "An array of options for a customizable product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Options") +} + +interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CategoryInterfaceTypeResolver") @doc(description: "CategoryInterface contains the full set of attributes that can be returned in a category search.") { + id: Int @doc(description: "An ID that uniquely identifies the category.") + description: String @doc(description: "An optional description of the category.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryHtmlAttribute") + name: String @doc(description: "The display name of the category.") + path: String @doc(description: "Category Path.") + path_in_store: String @doc(description: "Category path in store.") + url_key: String @doc(description: "The url key assigned to the category.") + url_path: String @doc(description: "The url path assigned to the category.") + position: Int @doc(description: "The position of the category relative to other categories at the same level in tree.") + level: Int @doc(description: "Indicates the depth of the category within the tree.") + created_at: String @doc(description: "Timestamp indicating when the category was created.") + updated_at: String @doc(description: "Timestamp indicating when the category was updated.") + product_count: Int @doc(description: "The number of products in the category.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\ProductsCount") + default_sort_by: String @doc(description: "The attribute to use for sorting.") products( pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") - ): CategoryProducts @doc(description: "The list of products assigned to the category") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") - breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") + ): CategoryProducts @doc(description: "The list of products assigned to the category.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") + breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") } -type Breadcrumb @doc(description: "Breadcrumb item"){ - category_id: Int @doc(description: "Category ID") - category_name: String @doc(description: "Category name") - category_level: Int @doc(description: "Category level") - category_url_key: String @doc(description: "Category URL key") +type Breadcrumb @doc(description: "Breadcrumb item."){ + category_id: Int @doc(description: "Category ID.") + category_name: String @doc(description: "Category name.") + category_level: Int @doc(description: "Category level.") + category_url_key: String @doc(description: "Category URL key.") } -type CustomizableRadioOption implements CustomizableOptionInterface @doc(description: "CustomizableRadioOption contains information about a set of radio buttons that are defined as part of a customizable option") { - value: [CustomizableRadioValue] @doc(description: "An array that defines a set of radio buttons") +type CustomizableRadioOption implements CustomizableOptionInterface @doc(description: "CustomizableRadioOption contains information about a set of radio buttons that are defined as part of a customizable option.") { + value: [CustomizableRadioValue] @doc(description: "An array that defines a set of radio buttons.") } -type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines the price and sku of a product whose page contains a customized set of radio buttons") { - option_type_id: Int @doc(description: "The ID assigned to the value") - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - title: String @doc(description: "The display name for this option") - sort_order: Int @doc(description: "The order in which the radio button is displayed") +type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines the price and sku of a product whose page contains a customized set of radio buttons.") { + option_type_id: Int @doc(description: "The ID assigned to the value.") + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + title: String @doc(description: "The display name for this option.") + sort_order: Int @doc(description: "The order in which the radio button is displayed.") } -type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(description: "CustomizableCheckbbixOption contains information about a set of checkbox values that are defined as part of a customizable option") { - value: [CustomizableCheckboxValue] @doc(description: "An array that defines a set of checkbox values") +type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(description: "CustomizableCheckbbixOption contains information about a set of checkbox values that are defined as part of a customizable option.") { + value: [CustomizableCheckboxValue] @doc(description: "An array that defines a set of checkbox values.") } -type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values") { - option_type_id: Int @doc(description: "The ID assigned to the value") - price: Float @doc(description: "The price assigned to this option") - price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") - sku: String @doc(description: "The Stock Keeping Unit for this option") - title: String @doc(description: "The display name for this option") - sort_order: Int @doc(description: "The order in which the checkbox value is displayed") +type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values.") { + option_type_id: Int @doc(description: "The ID assigned to the value.") + price: Float @doc(description: "The price assigned to this option.") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.") + sku: String @doc(description: "The Stock Keeping Unit for this option.") + title: String @doc(description: "The display name for this option.") + sort_order: Int @doc(description: "The order in which the checkbox value is displayed.") } -type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory") { +type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory.") { } -type SimpleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "A simple product is tangible and are usually sold as single units or in fixed quantities") +type SimpleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "A simple product is tangible and are usually sold as single units or in fixed quantities.") { } -type Products @doc(description: "The Products object is the top-level object returned in a product search") { - items: [ProductInterface] @doc(description: "An array of products that match the specified search criteria") - page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query") - total_count: Int @doc(description: "The number of products returned") - filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array") - sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\SortFields") +type Products @doc(description: "The Products object is the top-level object returned in a product search.") { + items: [ProductInterface] @doc(description: "An array of products that match the specified search criteria.") + page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query.") + total_count: Int @doc(description: "The number of products returned.") + filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array.") + sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\SortFields") } -type CategoryProducts @doc(description: "The category products object returned in the Category query") { - items: [ProductInterface] @doc(description: "An array of products that are assigned to the category") - page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query") - total_count: Int @doc(description: "The number of products returned") +type CategoryProducts @doc(description: "The category products object returned in the Category query.") { + items: [ProductInterface] @doc(description: "An array of products that are assigned to the category.") + page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query.") + total_count: Int @doc(description: "The number of products returned.") } input ProductFilterInput @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.") - sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") + sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: FilterTypeInput @doc(description: "Detailed information about the product. The value can include simple HTML tags.") short_description: FilterTypeInput @doc(description: "A short description of the product. Its use depends on the theme.") - price: FilterTypeInput @doc(description: "The price of an item") + price: FilterTypeInput @doc(description: "The price of an item.") special_price: FilterTypeInput @doc(description: "The discounted price of the product. Do not include the currency code.") - special_from_date: FilterTypeInput @doc(description: "The beginning date that a product has a special price") - special_to_date: FilterTypeInput @doc(description: "The end date that a product has a special price") - weight: FilterTypeInput @doc(description: "The weight of the item, in units defined by the store") - manufacturer: FilterTypeInput @doc(description: "A number representing the product's manufacturer") - meta_title: FilterTypeInput @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists") - meta_keyword: FilterTypeInput @doc(description: "A comma-separated list of keywords that are visible only to search engines") - meta_description: FilterTypeInput @doc(description: "A brief overview of the product for search results listings, maximum 255 characters") - image: FilterTypeInput @doc(description: "The relative path to the main image on the product page") - small_image: FilterTypeInput @doc(description: "The relative path to the small image, which is used on catalog pages") - thumbnail: FilterTypeInput @doc(description: "The relative path to the product's thumbnail image") - tier_price: FilterTypeInput @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached") - news_from_date: FilterTypeInput @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") - news_to_date: FilterTypeInput @doc(description: "The end date for new product listings") - custom_layout_update: FilterTypeInput @doc(description: "XML code that is applied as a layout update to the product page") + special_from_date: FilterTypeInput @doc(description: "The beginning date that a product has a special price.") + special_to_date: FilterTypeInput @doc(description: "The end date that a product has a special price.") + weight: FilterTypeInput @doc(description: "The weight of the item, in units defined by the store.") + manufacturer: FilterTypeInput @doc(description: "A number representing the product's manufacturer.") + meta_title: FilterTypeInput @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists.") + meta_keyword: FilterTypeInput @doc(description: "A comma-separated list of keywords that are visible only to search engines.") + meta_description: FilterTypeInput @doc(description: "A brief overview of the product for search results listings, maximum 255 characters.") + image: FilterTypeInput @doc(description: "The relative path to the main image on the product page.") + small_image: FilterTypeInput @doc(description: "The relative path to the small image, which is used on catalog pages.") + thumbnail: FilterTypeInput @doc(description: "The relative path to the product's thumbnail image.") + tier_price: FilterTypeInput @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached.") + news_from_date: FilterTypeInput @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product.") + news_to_date: FilterTypeInput @doc(description: "The end date for new product listings.") + custom_layout_update: FilterTypeInput @doc(description: "XML code that is applied as a layout update to the product page.") min_price: FilterTypeInput @doc(description:"The numeric minimal price of the product. Do not include the currency code.") max_price: FilterTypeInput @doc(description:"The numeric maximal price of the product. Do not include the currency code.") - category_id: FilterTypeInput @doc(description: "Category ID the product belongs to") - options_container: FilterTypeInput @doc(description: "If the product has multiple options, determines where they appear on the product page") - required_options: FilterTypeInput @doc(description: "Indicates whether the product has required options") - has_options: FilterTypeInput @doc(description: "Indicates whether additional attributes have been created for the product") - image_label: FilterTypeInput @doc(description: "The label assigned to a product image") - small_image_label: FilterTypeInput @doc(description: "The label assigned to a product's small image") - thumbnail_label: FilterTypeInput @doc(description: "The label assigned to a product's thumbnail image") - created_at: FilterTypeInput @doc(description: "Timestamp indicating when the product was created") - updated_at: FilterTypeInput @doc(description: "Timestamp indicating when the product was updated") - country_of_manufacture: FilterTypeInput @doc(description: "The product's country of origin") - custom_layout: FilterTypeInput @doc(description: "The name of a custom layout") - gift_message_available: FilterTypeInput @doc(description: "Indicates whether a gift message is available") - or: ProductFilterInput @doc(description: "The keyword required to perform a logical OR comparison") -} - -type ProductMediaGalleryEntriesContent @doc(description: "ProductMediaGalleryEntriesContent contains an image in base64 format and basic information about the image") { - base64_encoded_data: String @doc(description: "The image in base64 format") - type: String @doc(description: "The MIME type of the file, such as image/png") - name: String @doc(description: "The file name of the image") -} - -type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalleryEntriesVideoContent contains a link to a video file and basic information about the video") { - media_type: String @doc(description: "Must be external-video") - video_provider: String @doc(description: "Describes the video source") - video_url: String @doc(description: "The URL to the video") - video_title: String @doc(description: "The title of the video") - video_description: String @doc(description: "A description of the video") - video_metadata: String @doc(description: "Optional data about the video") -} - -input ProductSortInput @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order") { + category_id: FilterTypeInput @doc(description: "Category ID the product belongs to.") + options_container: FilterTypeInput @doc(description: "If the product has multiple options, determines where they appear on the product page.") + required_options: FilterTypeInput @doc(description: "Indicates whether the product has required options.") + has_options: FilterTypeInput @doc(description: "Indicates whether additional attributes have been created for the product.") + image_label: FilterTypeInput @doc(description: "The label assigned to a product image.") + small_image_label: FilterTypeInput @doc(description: "The label assigned to a product's small image.") + thumbnail_label: FilterTypeInput @doc(description: "The label assigned to a product's thumbnail image.") + created_at: FilterTypeInput @doc(description: "Timestamp indicating when the product was created.") + updated_at: FilterTypeInput @doc(description: "Timestamp indicating when the product was updated.") + country_of_manufacture: FilterTypeInput @doc(description: "The product's country of origin.") + custom_layout: FilterTypeInput @doc(description: "The name of a custom layout.") + gift_message_available: FilterTypeInput @doc(description: "Indicates whether a gift message is available.") + or: ProductFilterInput @doc(description: "The keyword required to perform a logical OR comparison.") +} + +type ProductMediaGalleryEntriesContent @doc(description: "ProductMediaGalleryEntriesContent contains an image in base64 format and basic information about the image.") { + base64_encoded_data: String @doc(description: "The image in base64 format.") + type: String @doc(description: "The MIME type of the file, such as image/png.") + name: String @doc(description: "The file name of the image.") +} + +type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalleryEntriesVideoContent contains a link to a video file and basic information about the video.") { + media_type: String @doc(description: "Must be external-video.") + video_provider: String @doc(description: "Describes the video source.") + video_url: String @doc(description: "The URL to the video.") + video_title: String @doc(description: "The title of the video.") + video_description: String @doc(description: "A description of the video.") + video_metadata: String @doc(description: "Optional data about the video.") +} + +input ProductSortInput @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { name: SortEnum @doc(description: "The product name. Customers use this name to identify the product.") - sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") + sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: SortEnum @doc(description: "Detailed information about the product. The value can include simple HTML tags.") short_description: SortEnum @doc(description: "A short description of the product. Its use depends on the theme.") - price: SortEnum @doc(description: "The price of the item") - special_price: SortEnum @doc(description: "The discounted price of the product") - special_from_date: SortEnum @doc(description: "The beginning date that a product has a special price") - special_to_date: SortEnum @doc(description: "The end date that a product has a special price") - weight: SortEnum @doc(description: "The weight of the item, in units defined by the store") - manufacturer: SortEnum @doc(description: "A number representing the product's manufacturer") - meta_title: SortEnum @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists") - meta_keyword: SortEnum @doc(description: "A comma-separated list of keywords that are visible only to search engines") - meta_description: SortEnum @doc(description: "A brief overview of the product for search results listings, maximum 255 characters") - image: SortEnum @doc(description: "The relative path to the main image on the product page") - small_image: SortEnum @doc(description: "The relative path to the small image, which is used on catalog pages") - thumbnail: SortEnum @doc(description: "The relative path to the product's thumbnail image") - tier_price: SortEnum @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached") - news_from_date: SortEnum @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") - news_to_date: SortEnum @doc(description: "The end date for new product listings") - custom_layout_update: SortEnum @doc(description: "XML code that is applied as a layout update to the product page") - options_container: SortEnum @doc(description: "If the product has multiple options, determines where they appear on the product page") - required_options: SortEnum @doc(description: "Indicates whether the product has required options") - has_options: SortEnum @doc(description: "Indicates whether additional attributes have been created for the product") - image_label: SortEnum @doc(description: "The label assigned to a product image") - small_image_label: SortEnum @doc(description: "The label assigned to a product's small image") - thumbnail_label: SortEnum @doc(description: "The label assigned to a product's thumbnail image") - created_at: SortEnum @doc(description: "Timestamp indicating when the product was created") - updated_at: SortEnum @doc(description: "Timestamp indicating when the product was updated") - country_of_manufacture: SortEnum @doc(description: "The product's country of origin") - custom_layout: SortEnum @doc(description: "The name of a custom layout") - gift_message_available: SortEnum @doc(description: "Indicates whether a gift message is available") -} - -type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product") { - id: Int @doc(description: "The identifier assigned to the object") - media_type: String @doc(description: "image or video") - label: String @doc(description: "The alt text displayed on the UI when the user points to the image") - position: Int @doc(description: "The media item's position after it has been sorted") - disabled: Boolean @doc(description: "Whether the image is hidden from vie") - types: [String] @doc(description: "Array of image types. It can have the following values: image, small_image, thumbnail") - file: String @doc(description: "The path of the image on the server") - content: ProductMediaGalleryEntriesContent @doc(description: "Contains a ProductMediaGalleryEntriesContent object") - video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object") + price: SortEnum @doc(description: "The price of the item.") + special_price: SortEnum @doc(description: "The discounted price of the product.") + special_from_date: SortEnum @doc(description: "The beginning date that a product has a special price.") + special_to_date: SortEnum @doc(description: "The end date that a product has a special price.") + weight: SortEnum @doc(description: "The weight of the item, in units defined by the store.") + manufacturer: SortEnum @doc(description: "A number representing the product's manufacturer.") + meta_title: SortEnum @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists.") + meta_keyword: SortEnum @doc(description: "A comma-separated list of keywords that are visible only to search engines.") + meta_description: SortEnum @doc(description: "A brief overview of the product for search results listings, maximum 255 characters.") + image: SortEnum @doc(description: "The relative path to the main image on the product page.") + small_image: SortEnum @doc(description: "The relative path to the small image, which is used on catalog pages.") + thumbnail: SortEnum @doc(description: "The relative path to the product's thumbnail image.") + tier_price: SortEnum @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached.") + news_from_date: SortEnum @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product.") + news_to_date: SortEnum @doc(description: "The end date for new product listings.") + custom_layout_update: SortEnum @doc(description: "XML code that is applied as a layout update to the product page.") + options_container: SortEnum @doc(description: "If the product has multiple options, determines where they appear on the product page.") + required_options: SortEnum @doc(description: "Indicates whether the product has required options.") + has_options: SortEnum @doc(description: "Indicates whether additional attributes have been created for the product.") + image_label: SortEnum @doc(description: "The label assigned to a product image.") + small_image_label: SortEnum @doc(description: "The label assigned to a product's small image.") + thumbnail_label: SortEnum @doc(description: "The label assigned to a product's thumbnail image.") + created_at: SortEnum @doc(description: "Timestamp indicating when the product was created.") + updated_at: SortEnum @doc(description: "Timestamp indicating when the product was updated.") + country_of_manufacture: SortEnum @doc(description: "The product's country of origin.") + custom_layout: SortEnum @doc(description: "The name of a custom layout.") + gift_message_available: SortEnum @doc(description: "Indicates whether a gift message is available.") +} + +type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") { + id: Int @doc(description: "The identifier assigned to the object.") + media_type: String @doc(description: "image or video.") + label: String @doc(description: "The alt text displayed on the UI when the user points to the image.") + position: Int @doc(description: "The media item's position after it has been sorted.") + disabled: Boolean @doc(description: "Whether the image is hidden from vie.") + types: [String] @doc(description: "Array of image types. It can have the following values: image, small_image, thumbnail.") + file: String @doc(description: "The path of the image on the server.") + content: ProductMediaGalleryEntriesContent @doc(description: "Contains a ProductMediaGalleryEntriesContent object.") + video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object.") } type LayerFilter { - name: String @doc(description: "Layered navigation filter name") - request_var: String @doc(description: "Request variable name for filter query") - filter_items_count: Int @doc(description: "Count of filter items in filter group") - filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items") + name: String @doc(description: "Layered navigation filter name.") + request_var: String @doc(description: "Request variable name for filter query.") + filter_items_count: Int @doc(description: "Count of filter items in filter group.") + filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") } interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\LayerFilterItemTypeResolverComposite") { - label: String @doc(description: "Filter label") - value_string: String @doc(description: "Value for filter request variable to be used in query") - items_count: Int @doc(description: "Count of items by filter") + label: String @doc(description: "Filter label.") + value_string: String @doc(description: "Value for filter request variable to be used in query.") + items_count: Int @doc(description: "Count of items by filter.") } type LayerFilterItem implements LayerFilterItemInterface { @@ -393,11 +397,27 @@ type LayerFilterItem implements LayerFilterItemInterface { } type SortField { - value: String @doc(description: "Attribute code of sort field") - label: String @doc(description: "Label of sort field") + value: String @doc(description: "Attribute code of sort field.") + label: String @doc(description: "Label of sort field.") +} + +type SortFields @doc(description: "SortFields contains a default value for sort fields and all available sort fields.") { + default: String @doc(description: "Default value of sort fields.") + options: [SortField] @doc(description: "Available sort fields.") +} + +type StoreConfig @doc(description: "The type contains information about a store config.") { + product_url_suffix : String @doc(description: "Product URL Suffix.") + category_url_suffix : String @doc(description: "Category URL Suffix.") + title_separator : String @doc(description: "Page Title Separator.") + list_mode : String @doc(description: "List Mode.") + grid_per_page_values : String @doc(description: "Products per Page on Grid Allowed Values.") + list_per_page_values : String @doc(description: "Products per Page on List Allowed Values.") + grid_per_page : Int @doc(description: "Products per Page on Grid Default Value.") + list_per_page : Int @doc(description: "Products per Page on List Default Value.") + catalog_default_sort_by : String @doc(description: "Default Sort By.") } -type SortFields @doc(description: "SortFields contains a default value for sort fields and all available sort fields") { - default: String @doc(description: "Default value of sort fields") - options: [SortField] @doc(description: "Available sort fields") +type ProductVideo @doc(description: "Contains information about a product video.") implements MediaGalleryInterface { + video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object.") } diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 8b4d4454be617..01a4d90c9517f 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -444,8 +444,11 @@ protected function initCategories() if ($pathSize > 1) { $path = []; for ($i = 1; $i < $pathSize; $i++) { - $name = $collection->getItemById($structure[$i])->getName(); - $path[] = $this->quoteCategoryDelimiter($name); + $childCategory = $collection->getItemById($structure[$i]); + if ($childCategory) { + $name = $childCategory->getName(); + $path[] = $this->quoteCategoryDelimiter($name); + } } $this->_rootCategories[$category->getId()] = array_shift($path); if ($pathSize > 2) { @@ -673,8 +676,8 @@ protected function prepareLinks(array $productIds) /** * Update data row with information about categories. Return true, if data row was updated * - * @param array &$dataRow - * @param array &$rowCategories + * @param array $dataRow + * @param array $rowCategories * @param int $productId * @return bool */ @@ -840,6 +843,7 @@ protected function paginateCollection($page, $pageSize) public function export() { //Execution time may be very long + // phpcs:ignore Magento2.Functions.DiscouragedFunction set_time_limit(0); $writer = $this->getWriter(); @@ -964,6 +968,7 @@ protected function loadCollection(): array * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * phpcs:disable Generic.Metrics.NestingLevel */ protected function collectRawData() { @@ -1058,6 +1063,7 @@ protected function collectRawData() return $data; } + //phpcs:enable Generic.Metrics.NestingLevel /** * Wrap values with double quotes if "Fields Enclosure" option is enabled diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index bbfc78fadb026..dee4dc6904132 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -9,6 +9,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Config as CatalogConfig; use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ResourceModel\Product\Link; use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; @@ -37,6 +38,7 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @since 100.0.2 */ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity @@ -300,6 +302,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually', ValidatorInterface::ERROR_DUPLICATE_MULTISELECT_VALUES => 'Value for multiselect attribute %s contains duplicated values', 'invalidNewToDateValue' => 'Make sure new_to_date is later than or the same as new_from_date', + // Can't add new translated strings in patch release + 'invalidLayoutUpdate' => 'Invalid format.', + 'insufficientPermissions' => 'Invalid format.', ]; //@codingStandardsIgnoreEnd @@ -1000,10 +1005,12 @@ public function setParameters(array $params) */ public function deleteProductsForReplacement() { - $this->setParameters(array_merge( - $this->getParameters(), - ['behavior' => Import::BEHAVIOR_DELETE] - )); + $this->setParameters( + array_merge( + $this->getParameters(), + ['behavior' => Import::BEHAVIOR_DELETE] + ) + ); $this->_deleteProducts(); return $this; @@ -1092,10 +1099,12 @@ protected function _replaceProducts() $this->deleteProductsForReplacement(); $this->_oldSku = $this->skuProcessor->reloadOldSkus()->getOldSkus(); $this->_validatedRows = null; - $this->setParameters(array_merge( - $this->getParameters(), - ['behavior' => Import::BEHAVIOR_APPEND] - )); + $this->setParameters( + array_merge( + $this->getParameters(), + ['behavior' => Import::BEHAVIOR_APPEND] + ) + ); $this->_saveProductsData(); return $this; @@ -1245,13 +1254,10 @@ protected function _prepareRowForDb(array $rowData) * Must be called after ALL products saving done. * * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveLinks() { + /** @var Link $resource */ $resource = $this->_linkFactory->create(); $mainTable = $resource->getMainTable(); $positionAttrId = []; @@ -1269,114 +1275,10 @@ protected function _saveLinks() $positionAttrId[$linkId] = $this->_connection->fetchOne($select, $bind); } while ($bunch = $this->_dataSourceModel->getNextBunch()) { - $productIds = []; - $linkRows = []; - $positionRows = []; - - foreach ($bunch as $rowNum => $rowData) { - if (!$this->isRowAllowedToImport($rowData, $rowNum)) { - continue; - } - - $sku = $rowData[self::COL_SKU]; - - $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()]; - $productLinkKeys = []; - $select = $this->_connection->select()->from( - $resource->getTable('catalog_product_link'), - ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id'] - )->where( - 'product_id = :product_id' - ); - $bind = [':product_id' => $productId]; - foreach ($this->_connection->fetchAll($select, $bind) as $linkData) { - $linkKey = "{$productId}-{$linkData['linked_id']}-{$linkData['link_type_id']}"; - $productLinkKeys[$linkKey] = $linkData['id']; - } - foreach ($this->_linkNameToId as $linkName => $linkId) { - $productIds[] = $productId; - if (isset($rowData[$linkName . 'sku'])) { - $linkSkus = explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); - $linkPositions = !empty($rowData[$linkName . 'position']) - ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position']) - : []; - foreach ($linkSkus as $linkedKey => $linkedSku) { - $linkedSku = trim($linkedSku); - if (($this->skuProcessor->getNewSku($linkedSku) !== null || $this->isSkuExist($linkedSku)) - && strcasecmp($linkedSku, $sku) !== 0 - ) { - $newSku = $this->skuProcessor->getNewSku($linkedSku); - if (!empty($newSku)) { - $linkedId = $newSku['entity_id']; - } else { - $linkedId = $this->getExistingSku($linkedSku)['entity_id']; - } - - if ($linkedId == null) { - // Import file links to a SKU which is skipped for some reason, - // which leads to a "NULL" - // link causing fatal errors. - $this->_logger->critical( - new \Exception( - sprintf( - 'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, ' . - 'Link type id: %d', - $sku, - $productId, - $linkedSku, - $linkId - ) - ) - ); - continue; - } - - $linkKey = "{$productId}-{$linkedId}-{$linkId}"; - if (empty($productLinkKeys[$linkKey])) { - $productLinkKeys[$linkKey] = $nextLinkId; - } - if (!isset($linkRows[$linkKey])) { - $linkRows[$linkKey] = [ - 'link_id' => $productLinkKeys[$linkKey], - 'product_id' => $productId, - 'linked_product_id' => $linkedId, - 'link_type_id' => $linkId, - ]; - } - if (!empty($linkPositions[$linkedKey])) { - $positionRows[] = [ - 'link_id' => $productLinkKeys[$linkKey], - 'product_link_attribute_id' => $positionAttrId[$linkId], - 'value' => $linkPositions[$linkedKey], - ]; - } - $nextLinkId++; - } - } - } - } - } - if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) { - $this->_connection->delete( - $mainTable, - $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds)) - ); - } - if ($linkRows) { - $this->_connection->insertOnDuplicate($mainTable, $linkRows, ['link_id']); - } - if ($positionRows) { - // process linked product positions - $this->_connection->insertOnDuplicate( - $resource->getAttributeTypeTable('int'), - $positionRows, - ['value'] - ); - } + $this->processLinkBunches($bunch, $resource, $nextLinkId, $positionAttrId); } return $this; } - // phpcs:enable /** * Save product attributes. @@ -1611,7 +1513,7 @@ public function getImagesFromRow(array $rowData) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException - * phpcs:disable Generic.Metrics.NestingLevel + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh */ protected function _saveProducts() { @@ -1818,42 +1720,44 @@ protected function _saveProducts() $rowData[$column] = $uploadedFile; } - if ($uploadedFile && !isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) { - if (isset($existingImages[$rowSku][$uploadedFile])) { - $currentFileData = $existingImages[$rowSku][$uploadedFile]; - if (isset($rowLabels[$column][$columnImageKey]) - && $rowLabels[$column][$columnImageKey] != - $currentFileData['label'] - ) { - $labelsForUpdate[] = [ - 'label' => $rowLabels[$column][$columnImageKey], - 'imageData' => $currentFileData - ]; - } - - if (array_key_exists($uploadedFile, $imageHiddenStates) - && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] - ) { - $imagesForChangeVisibility[] = [ - 'disabled' => $imageHiddenStates[$uploadedFile], - 'imageData' => $currentFileData - ]; - } - } else { - if ($column == self::COL_MEDIA_IMAGE) { - $rowData[$column][] = $uploadedFile; - } - $mediaGallery[$storeId][$rowSku][$uploadedFile] = [ - 'attribute_id' => $this->getMediaGalleryAttributeId(), - 'label' => isset($rowLabels[$column][$columnImageKey]) - ? $rowLabels[$column][$columnImageKey] - : '', - 'position' => ++$position, - 'disabled' => isset($imageHiddenStates[$columnImage]) - ? $imageHiddenStates[$columnImage] : '0', - 'value' => $uploadedFile, + if (!$uploadedFile || isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) { + continue; + } + + if (isset($existingImages[$rowSku][$uploadedFile])) { + $currentFileData = $existingImages[$rowSku][$uploadedFile]; + if (isset($rowLabels[$column][$columnImageKey]) + && $rowLabels[$column][$columnImageKey] != + $currentFileData['label'] + ) { + $labelsForUpdate[] = [ + 'label' => $rowLabels[$column][$columnImageKey], + 'imageData' => $currentFileData + ]; + } + + if (array_key_exists($uploadedFile, $imageHiddenStates) + && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] + ) { + $imagesForChangeVisibility[] = [ + 'disabled' => $imageHiddenStates[$uploadedFile], + 'imageData' => $currentFileData ]; } + } else { + if ($column == self::COL_MEDIA_IMAGE) { + $rowData[$column][] = $uploadedFile; + } + $mediaGallery[$storeId][$rowSku][$uploadedFile] = [ + 'attribute_id' => $this->getMediaGalleryAttributeId(), + 'label' => isset($rowLabels[$column][$columnImageKey]) + ? $rowLabels[$column][$columnImageKey] + : '', + 'position' => ++$position, + 'disabled' => isset($imageHiddenStates[$columnImage]) + ? $imageHiddenStates[$columnImage] : '0', + 'value' => $uploadedFile, + ]; } } } @@ -1984,7 +1888,6 @@ protected function _saveProducts() return $this; } - // phpcs:enable /** * Prepare array with image states (visible or hidden from product page) @@ -2590,6 +2493,12 @@ public function validateRow(array $rowData, $rowNum) */ private function isNeedToValidateUrlKey($rowData) { + if (!empty($rowData[self::COL_SKU]) && empty($rowData[self::URL_KEY]) + && $this->getBehavior() === Import::BEHAVIOR_APPEND + && $this->isSkuExist($rowData[self::COL_SKU])) { + return false; + } + return (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) && (empty($rowData[self::COL_VISIBILITY]) || $rowData[self::COL_VISIBILITY] @@ -2733,9 +2642,12 @@ public function parseMultiselectValues($values, $delimiter = self::PSEUDO_MULTI_ return explode($delimiter, $values); } if (preg_match_all('~"((?:[^"]|"")*)"~', $values, $matches)) { - return $values = array_map(function ($value) { - return str_replace('""', '"', $value); - }, $matches[1]); + return $values = array_map( + function ($value) { + return str_replace('""', '"', $value); + }, + $matches[1] + ); } return [$values]; } @@ -2906,10 +2818,12 @@ protected function getProductUrlSuffix($storeId = null) protected function getUrlKey($rowData) { if (!empty($rowData[self::URL_KEY])) { - return $this->productUrl->formatUrlKey($rowData[self::URL_KEY]); + $urlKey = (string) $rowData[self::URL_KEY]; + return trim(strtolower($urlKey)); } - if (!empty($rowData[self::COL_NAME])) { + if (!empty($rowData[self::COL_NAME]) + && (array_key_exists(self::URL_KEY, $rowData) || !$this->isSkuExist($rowData[self::COL_SKU]))) { return $this->productUrl->formatUrlKey($rowData[self::COL_NAME]); } @@ -3133,4 +3047,167 @@ private function getValidationErrorLevel($sku): string ? ProcessingError::ERROR_LEVEL_CRITICAL : ProcessingError::ERROR_LEVEL_NOT_CRITICAL; } + + /** + * Processes link bunches + * + * @param array $bunch + * @param Link $resource + * @param int $nextLinkId + * @param array $positionAttrId + * @return void + */ + private function processLinkBunches( + array $bunch, + Link $resource, + int $nextLinkId, + array $positionAttrId + ): void { + $productIds = []; + $linkRows = []; + $positionRows = []; + + $bunch = array_filter($bunch, [$this, 'isRowAllowedToImport'], ARRAY_FILTER_USE_BOTH); + foreach ($bunch as $rowData) { + $sku = $rowData[self::COL_SKU]; + $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()]; + $productIds[] = $productId; + $productLinkKeys = $this->fetchProductLinks($resource, $productId); + $linkNameToId = array_filter( + $this->_linkNameToId, + function ($linkName) use ($rowData) { + return isset($rowData[$linkName . 'sku']); + }, + ARRAY_FILTER_USE_KEY + ); + foreach ($linkNameToId as $linkName => $linkId) { + $linkSkus = explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); + $linkPositions = !empty($rowData[$linkName . 'position']) + ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position']) + : []; + + $linkSkus = array_filter( + $linkSkus, + function ($linkedSku) use ($sku) { + $linkedSku = trim($linkedSku); + return ($this->skuProcessor->getNewSku($linkedSku) !== null || $this->isSkuExist($linkedSku)) + && strcasecmp($linkedSku, $sku) !== 0; + } + ); + foreach ($linkSkus as $linkedKey => $linkedSku) { + $linkedId = $this->getProductLinkedId($linkedSku); + if ($linkedId == null) { + // Import file links to a SKU which is skipped for some reason, which leads to a "NULL" + // link causing fatal errors. + $formatStr = 'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, Link type id: %d'; + $exception = new \Exception(sprintf($formatStr, $sku, $productId, $linkedSku, $linkId)); + $this->_logger->critical($exception); + continue; + } + $linkKey = $this->composeLinkKey($productId, $linkedId, $linkId); + $productLinkKeys[$linkKey] = $productLinkKeys[$linkKey] ?? $nextLinkId; + + $linkRows[$linkKey] = $linkRows[$linkKey] ?? [ + 'link_id' => $productLinkKeys[$linkKey], + 'product_id' => $productId, + 'linked_product_id' => $linkedId, + 'link_type_id' => $linkId, + ]; + + if (!empty($linkPositions[$linkedKey])) { + $positionRows[] = [ + 'link_id' => $productLinkKeys[$linkKey], + 'product_link_attribute_id' => $positionAttrId[$linkId], + 'value' => $linkPositions[$linkedKey], + ]; + } + $nextLinkId++; + } + } + } + $this->saveLinksData($resource, $productIds, $linkRows, $positionRows); + } + + /** + * Fetches Product Links + * + * @param Link $resource + * @param int $productId + * @return array + */ + private function fetchProductLinks(Link $resource, int $productId) : array + { + $productLinkKeys = []; + $select = $this->_connection->select()->from( + $resource->getTable('catalog_product_link'), + ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id'] + )->where( + 'product_id = :product_id' + ); + $bind = [':product_id' => $productId]; + foreach ($this->_connection->fetchAll($select, $bind) as $linkData) { + $linkKey = $this->composeLinkKey($productId, $linkData['linked_id'], $linkData['link_type_id']); + $productLinkKeys[$linkKey] = $linkData['id']; + } + + return $productLinkKeys; + } + + /** + * Gets the Id of the Sku + * + * @param string $linkedSku + * @return int|null + */ + private function getProductLinkedId(string $linkedSku) : ?int + { + $linkedSku = trim($linkedSku); + $newSku = $this->skuProcessor->getNewSku($linkedSku); + $linkedId = !empty($newSku) ? $newSku['entity_id'] : $this->getExistingSku($linkedSku)['entity_id']; + return $linkedId; + } + + /** + * Saves information about product links + * + * @param Link $resource + * @param array $productIds + * @param array $linkRows + * @param array $positionRows + * @throws LocalizedException + */ + private function saveLinksData(Link $resource, array $productIds, array $linkRows, array $positionRows) + { + $mainTable = $resource->getMainTable(); + if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) { + $this->_connection->delete( + $mainTable, + $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds)) + ); + } + if ($linkRows) { + $this->_connection->insertOnDuplicate($mainTable, $linkRows, ['link_id']); + } + if ($positionRows) { + // process linked product positions + $this->_connection->insertOnDuplicate( + $resource->getAttributeTypeTable('int'), + $positionRows, + ['value'] + ); + } + } + + /** + * Composes the link key + * + * @param int $productId + * @param int $linkedId + * @param int $linkTypeId + * @return string + */ + private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId) : string + { + return "{$productId}-{$linkedId}-{$linkTypeId}"; + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 7435c0bebfc14..6cdafa7fc6f5a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -669,6 +669,7 @@ protected function _findNewOptionsWithTheSameTitles() * * @param array $sourceProductData * @return array + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProductData) { @@ -697,6 +698,7 @@ protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProduct * Find options with the same titles in DB * * @return array + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _findOldOptionsWithTheSameTitles() { @@ -730,6 +732,7 @@ protected function _findOldOptionsWithTheSameTitles() * Find source file options, which have analogs in DB with the same name, but with different type * * @return array + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _findNewOldOptionsTypeMismatch() { @@ -1067,7 +1070,7 @@ protected function _isSecondaryOptionRow(array $rowData) * * @param array &$options * @param array &$titles - * @param array $typeValues + * @param array $typeValues * @return bool */ protected function _isReadyForSaving(array &$options, array &$titles, array $typeValues) @@ -1414,9 +1417,9 @@ protected function _initProductsSku() /** * Collect custom option main data to import * - * @param array $rowData - * @param int &$prevOptionId - * @param int &$nextOptionId + * @param array $rowData + * @param int &$prevOptionId + * @param int &$nextOptionId * @param array &$products * @param array &$prices * @return array|null @@ -1454,9 +1457,9 @@ protected function _collectOptionMainData( /** * Collect custom option type data to import * - * @param array $rowData - * @param int &$prevOptionId - * @param int &$nextValueId + * @param array $rowData + * @param int &$prevOptionId + * @param int &$nextValueId * @param array &$typeValues * @param array &$typePrices * @param array &$typeTitles @@ -1504,6 +1507,9 @@ protected function _collectOptionTypeData( $specificTypeData = $this->_getSpecificTypeData($rowData, 0, false); //For others stores if ($specificTypeData) { + if (isset($specificTypeData['price'])) { + $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price']; + } $typeTitles[$nextValueId++][$this->_rowStoreId] = $specificTypeData['title']; } } @@ -1512,8 +1518,8 @@ protected function _collectOptionTypeData( /** * Collect custom option title to import * - * @param array $rowData - * @param int $prevOptionId + * @param array $rowData + * @param int $prevOptionId * @param array &$titles * @return void */ @@ -1788,29 +1794,30 @@ protected function _getPriceData(array $rowData, $optionId, $type) */ protected function _getSpecificTypeData(array $rowData, $optionTypeId, $defaultStore = true) { + $data = []; + $priceData = []; + $customOptionRowPrice = $rowData[self::COLUMN_ROW_PRICE]; + if (!empty($customOptionRowPrice) || $customOptionRowPrice === '0') { + $priceData['price'] = (double)rtrim($rowData[self::COLUMN_ROW_PRICE], '%'); + $priceData['price_type'] = ('%' == substr($rowData[self::COLUMN_ROW_PRICE], -1)) ? 'percent' : 'fixed'; + } if (!empty($rowData[self::COLUMN_ROW_TITLE]) && $defaultStore && empty($rowData[self::COLUMN_STORE])) { $valueData = [ 'option_type_id' => $optionTypeId, 'sort_order' => empty($rowData[self::COLUMN_ROW_SORT]) ? 0 : abs($rowData[self::COLUMN_ROW_SORT]), 'sku' => !empty($rowData[self::COLUMN_ROW_SKU]) ? $rowData[self::COLUMN_ROW_SKU] : '', ]; - - $priceData = false; - $customOptionRowPrice = $rowData[self::COLUMN_ROW_PRICE]; - if (!empty($customOptionRowPrice) || $customOptionRowPrice === '0') { - $priceData = [ - 'price' => (double)rtrim($rowData[self::COLUMN_ROW_PRICE], '%'), - 'price_type' => 'fixed', - ]; - if ('%' == substr($rowData[self::COLUMN_ROW_PRICE], -1)) { - $priceData['price_type'] = 'percent'; - } - } - return ['value' => $valueData, 'title' => $rowData[self::COLUMN_ROW_TITLE], 'price' => $priceData]; + $data['value'] = $valueData; + $data['title'] = $rowData[self::COLUMN_ROW_TITLE]; + $data['price'] = $priceData; } elseif (!empty($rowData[self::COLUMN_ROW_TITLE]) && !$defaultStore && !empty($rowData[self::COLUMN_STORE])) { - return ['title' => $rowData[self::COLUMN_ROW_TITLE]]; + if ($priceData) { + $data['price'] = $priceData; + } + $data['title'] = $rowData[self::COLUMN_ROW_TITLE]; } - return false; + + return $data ?: false; } /** @@ -1868,7 +1875,9 @@ protected function _saveTitles(array $titles) { $titleRows = []; foreach ($titles as $optionId => $storeInfo) { - foreach ($storeInfo as $storeId => $title) { + //for use default + $uniqStoreInfo = array_unique($storeInfo); + foreach ($uniqStoreInfo as $storeId => $title) { $titleRows[] = ['option_id' => $optionId, 'store_id' => $storeId, 'title' => $title]; } } @@ -1963,7 +1972,9 @@ protected function _saveSpecificTypeTitles(array $typeTitles) { $optionTypeTitleRows = []; foreach ($typeTitles as $optionTypeId => $storesData) { - foreach ($storesData as $storeId => $title) { + //for use default + $uniqStoresData = array_unique($storesData); + foreach ($uniqStoresData as $storeId => $title) { $optionTypeTitleRows[] = [ 'option_type_id' => $optionTypeId, 'store_id' => $storeId, diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdate.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdate.php new file mode 100644 index 0000000000000..99919628518c6 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdate.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Import\Product\Validator; + +use Magento\Framework\Config\ValidationStateInterface; +use Magento\Framework\View\Model\Layout\Update\ValidatorFactory; + +/** + * Validates layout and custom layout update fields + */ +class LayoutUpdate extends AbstractImportValidator +{ + private const ERROR_INVALID_LAYOUT_UPDATE = 'invalidLayoutUpdate'; + + /** + * @var ValidatorFactory + */ + private $layoutValidatorFactory; + + /** + * @var ValidationStateInterface + */ + private $validationState; + + /** + * @param ValidatorFactory $layoutValidatorFactory + * @param ValidationStateInterface $validationState + */ + public function __construct( + ValidatorFactory $layoutValidatorFactory, + ValidationStateInterface $validationState + ) { + $this->layoutValidatorFactory = $layoutValidatorFactory; + $this->validationState = $validationState; + } + + /** + * @inheritdoc + */ + public function isValid($value): bool + { + if (!empty($value['custom_layout_update']) && !$this->validateXml($value['custom_layout_update'])) { + $this->_addMessages( + [ + $this->context->retrieveMessageTemplate(self::ERROR_INVALID_LAYOUT_UPDATE) + ] + ); + return false; + } + + return true; + } + + /** + * Validate XML layout update + * + * @param string $xml + * @return bool + */ + private function validateXml(string $xml): bool + { + /** @var $layoutXmlValidator \Magento\Framework\View\Model\Layout\Update\Validator */ + $layoutXmlValidator = $this->layoutValidatorFactory->create( + [ + 'validationState' => $this->validationState, + ] + ); + + try { + if (!$layoutXmlValidator->isValid($xml)) { + return false; + } + } catch (\Exception $e) { + return false; + } + + return true; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdatePermissions.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdatePermissions.php new file mode 100644 index 0000000000000..50d38cedfb754 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/LayoutUpdatePermissions.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Import\Product\Validator; + +use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\AuthorizationInterface; +use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator; + +/** + * Validator to assert that the current user is allowed to make design updates if a layout is provided in the import + */ +class LayoutUpdatePermissions extends AbstractImportValidator +{ + private const ERROR_INSUFFICIENT_PERMISSIONS = 'insufficientPermissions'; + + /** + * @var UserContextInterface + */ + private $userContext; + + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * @var array + */ + private $allowedUserTypes = [ + UserContextInterface::USER_TYPE_ADMIN, + UserContextInterface::USER_TYPE_INTEGRATION + ]; + + /** + * @param UserContextInterface $userContext + * @param AuthorizationInterface $authorization + */ + public function __construct( + UserContextInterface $userContext, + AuthorizationInterface $authorization + ) { + $this->userContext = $userContext; + $this->authorization = $authorization; + } + + /** + * Validate that the current user is allowed to make design updates + * + * @param array $data + * @return boolean + */ + public function isValid($data): bool + { + if (empty($data['custom_layout_update'])) { + return true; + } + + $userType = $this->userContext->getUserType(); + $isValid = in_array($userType, $this->allowedUserTypes) + && $this->authorization->isAllowed('Magento_Catalog::edit_product_design'); + + if (!$isValid) { + $this->_addMessages( + [ + $this->context->retrieveMessageTemplate(self::ERROR_INSUFFICIENT_PERMISSIONS), + ] + ); + } + + return $isValid; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php index 4ce1c0e39d6de..09c3cc4daf1d9 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php @@ -7,6 +7,8 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\DriverPool; /** @@ -111,13 +113,18 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader */ private $random; + /** + * @var Filesystem + */ + private $fileSystem; + /** * @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDb * @param \Magento\MediaStorage\Helper\File\Storage $coreFileStorage * @param \Magento\Framework\Image\AdapterFactory $imageFactory * @param \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator - * @param \Magento\Framework\Filesystem $filesystem - * @param \Magento\Framework\Filesystem\File\ReadFactory $readFactory + * @param Filesystem $filesystem + * @param Filesystem\File\ReadFactory $readFactory * @param string|null $filePath * @param \Magento\Framework\Math\Random|null $random * @throws \Magento\Framework\Exception\FileSystemException @@ -128,8 +135,8 @@ public function __construct( \Magento\MediaStorage\Helper\File\Storage $coreFileStorage, \Magento\Framework\Image\AdapterFactory $imageFactory, \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator, - \Magento\Framework\Filesystem $filesystem, - \Magento\Framework\Filesystem\File\ReadFactory $readFactory, + Filesystem $filesystem, + Filesystem\File\ReadFactory $readFactory, $filePath = null, \Magento\Framework\Math\Random $random = null ) { @@ -137,6 +144,7 @@ public function __construct( $this->_coreFileStorageDb = $coreFileStorageDb; $this->_coreFileStorage = $coreFileStorage; $this->_validator = $validator; + $this->fileSystem = $filesystem; $this->_directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); $this->_readFactory = $readFactory; if ($filePath !== null) { @@ -236,7 +244,20 @@ private function downloadFileFromUrl($url, $driver) */ protected function _setUploadFile($filePath) { - if (!$this->_directory->isReadable($filePath)) { + try { + $fullPath = $this->_directory->getAbsolutePath($filePath); + if ($this->getTmpDir()) { + $tmpDir = $this->fileSystem->getDirectoryReadByPath( + $this->_directory->getAbsolutePath($this->getTmpDir()) + ); + } else { + $tmpDir = $this->_directory; + } + $readable = $tmpDir->isReadable($fullPath); + } catch (ValidatorException $exception) { + $readable = false; + } + if (!$readable) { throw new \Magento\Framework\Exception\LocalizedException( __('File \'%1\' was not found or has read restriction.', $filePath) ); diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml index 7f8a854d8fb13..262e5d3b3465f 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml @@ -55,6 +55,7 @@ </arguments> <reloadPage stepKey="refreshPage"/> <waitForPageLoad time="30" stepKey="waitFormReload"/> + <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> <click stepKey="clickSelectBtn" selector="{{AdminExportAttributeSection.selectByIndex(rowIndex)}}"/> <click stepKey="clickOnDelete" selector="{{AdminExportAttributeSection.delete(rowIndex)}}" after="clickSelectBtn"/> <waitForElementVisible selector="{{AdminProductGridConfirmActionSection.title}}" stepKey="waitForConfirmModal"/> @@ -62,4 +63,26 @@ <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> </actionGroup> + + <actionGroup name="deleteAllExportedFiles"> + <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> + <executeInSelenium + function=" + function ($webdriver) use ($I) { + $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//button')); + while(!empty($buttons)) { + $buttons[0]->click(); + $I->waitForElementVisible('//tr[@data-repeat-index=\'0\']//a[text()=\'Delete\']', 10); + $deleteButton = $webdriver->findElement(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//a[text()=\'Delete\']')); + $deleteButton->click(); + $I->waitForElementVisible('.modal-popup.confirm button.action-accept', 10); + $I->click('.modal-popup.confirm button.action-accept'); + $I->waitForPageLoad(60); + $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//button')); + } + }" + stepKey="deleteAllExportedFilesOneByOne"/> + <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml index b571b32331dde..74345e64a7c9a 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml @@ -18,9 +18,6 @@ <testCaseId value="MC-14008"/> <group value="catalog_import_export"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-15934"/> - </skip> </annotations> <before> <!--Create bundle product with dynamic price with two simple products --> @@ -83,19 +80,15 @@ <requiredEntity createDataKey="secondSimpleProductForFixedWithAttribute"/> </createData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete products creations --> <deleteData createDataKey="createDynamicBundleProduct" stepKey="deleteDynamicBundleProduct"/> <deleteData createDataKey="firstSimpleProductForDynamic" stepKey="deleteFirstSimpleProductForDynamic"/> @@ -108,6 +101,10 @@ <deleteData createDataKey="secondSimpleProductForFixedWithAttribute" stepKey="deleteSecondSimpleProductForFixedWithAttribute"/> <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -120,6 +117,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml index a587d71ba0e68..b0ac6a4bc95ac 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml @@ -48,19 +48,15 @@ <requiredEntity createDataKey="createSecondSimpleProduct"/> </updateData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Deleted created products --> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> @@ -69,6 +65,10 @@ <!-- Delete category --> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -82,6 +82,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index 6f64da4693692..1870cb21bd55b 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -73,19 +73,15 @@ <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> @@ -93,6 +89,10 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -109,6 +109,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index 1442311709b3d..f6690199d63fe 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -89,19 +89,15 @@ <requiredEntity createDataKey="createConfigProduct"/> </createData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> @@ -109,6 +105,10 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -124,6 +124,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml index 491d20604a08b..271b4621d1a96 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml @@ -71,19 +71,15 @@ <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete simple product --> <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> @@ -94,6 +90,10 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -107,6 +107,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml index 365139eda06c1..238a3286dc40d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml @@ -18,9 +18,6 @@ <testCaseId value="MC-14007"/> <group value="catalog_import_export"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-15934"/> - </skip> </annotations> <before> <!-- Create simple product with custom attribute set --> @@ -31,24 +28,24 @@ <requiredEntity createDataKey="createAttributeSet"/> </createData> - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Run cron twice --> <magentoCLI command="cron:run" stepKey="runCron1"/> <magentoCLI command="cron:run" stepKey="runCron2"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete product creations --> <deleteData createDataKey="createSimpleProductWithCustomAttributeSet" stepKey="deleteSimpleProductWithCustomAttributeSet"/> <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete exported file --> + <actionGroup ref="deleteExportedFile" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -62,6 +59,7 @@ <!-- Run cron --> <magentoCLI command="cron:run" stepKey="runCron3"/> + <magentoCLI command="cron:run" stepKey="runCron4"/> <!-- Download product --> <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdatePermissionsTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdatePermissionsTest.php new file mode 100644 index 0000000000000..e018fc0cf5ccf --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdatePermissionsTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Test\Unit\Model\Import\Product\Validator; + +use Magento\CatalogImportExport\Model\Import\Product; +use Magento\Authorization\Model\UserContextInterface; +use Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdatePermissions; +use Magento\Framework\AuthorizationInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Test validation for layout update permissions + */ +class LayoutUpdatePermissionsTest extends TestCase +{ + /** + * @var LayoutUpdatePermissions|MockObject + */ + private $validator; + + /** + * @var UserContextInterface|MockObject + */ + private $userContext; + + /** + * @var AuthorizationInterface|MockObject + */ + private $authorization; + + /** + * @var Product + */ + private $context; + + protected function setUp() + { + $this->userContext = $this->createMock(UserContextInterface::class); + $this->authorization = $this->createMock(AuthorizationInterface::class); + $this->context = $this->createMock(Product::class); + $this->context + ->method('retrieveMessageTemplate') + ->with('insufficientPermissions') + ->willReturn('oh no'); + $this->validator = new LayoutUpdatePermissions( + $this->userContext, + $this->authorization + ); + $this->validator->init($this->context); + } + + /** + * @param $value + * @param $userContext + * @param $isAllowed + * @param $isValid + * @dataProvider configurationsProvider + */ + public function testValidationConfiguration($value, $userContext, $isAllowed, $isValid) + { + $this->userContext + ->method('getUserType') + ->willReturn($userContext); + + $this->authorization + ->method('isAllowed') + ->with('Magento_Catalog::edit_product_design') + ->willReturn($isAllowed); + + $result = $this->validator->isValid(['custom_layout_update' => $value]); + $messages = $this->validator->getMessages(); + + self::assertSame($isValid, $result); + + if ($isValid) { + self::assertSame([], $messages); + } else { + self::assertSame(['oh no'], $messages); + } + } + + public function configurationsProvider() + { + return [ + ['', null, null, true], + [null, null, null, true], + ['foo', UserContextInterface::USER_TYPE_ADMIN, true, true], + ['foo', UserContextInterface::USER_TYPE_INTEGRATION, true, true], + ['foo', UserContextInterface::USER_TYPE_ADMIN, false, false], + ['foo', UserContextInterface::USER_TYPE_INTEGRATION, false, false], + ['foo', 'something', null, false], + ]; + } +} diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdateTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdateTest.php new file mode 100644 index 0000000000000..d1e8b879f6a08 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/LayoutUpdateTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Test\Unit\Model\Import\Product\Validator; + +use Magento\CatalogImportExport\Model\Import\Product; +use Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate; +use Magento\Framework\Config\ValidationStateInterface; +use Magento\Framework\View\Model\Layout\Update\Validator; +use Magento\Framework\View\Model\Layout\Update\ValidatorFactory; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Test validation for layout update + */ +class LayoutUpdateTest extends TestCase +{ + /** + * @var LayoutUpdate|MockObject + */ + private $validator; + + /** + * @var Validator|MockObject + */ + private $layoutValidator; + + protected function setUp() + { + $validatorFactory = $this->createMock(ValidatorFactory::class); + $validationState = $this->createMock(ValidationStateInterface::class); + $this->layoutValidator = $this->createMock(Validator::class); + $validatorFactory->method('create') + ->with(['validationState' => $validationState]) + ->willReturn($this->layoutValidator); + + $this->validator = new LayoutUpdate( + $validatorFactory, + $validationState + ); + } + + public function testValidationIsSkippedWithDataNotPresent() + { + $this->layoutValidator + ->expects($this->never()) + ->method('isValid'); + + $result = $this->validator->isValid([]); + self::assertTrue($result); + } + + public function testValidationFailsProperly() + { + $this->layoutValidator + ->method('isValid') + ->with('foo') + ->willReturn(false); + + $contextMock = $this->createMock(Product::class); + $contextMock + ->method('retrieveMessageTemplate') + ->with('invalidLayoutUpdate') + ->willReturn('oh no'); + $this->validator->init($contextMock); + + $result = $this->validator->isValid(['custom_layout_update' => 'foo']); + $messages = $this->validator->getMessages(); + self::assertFalse($result); + self::assertSame(['oh no'], $messages); + } + + public function testInvalidDataException() + { + $this->layoutValidator + ->method('isValid') + ->willThrowException(new \Exception('foo')); + + $contextMock = $this->createMock(Product::class); + $contextMock + ->method('retrieveMessageTemplate') + ->with('invalidLayoutUpdate') + ->willReturn('oh no'); + $this->validator->init($contextMock); + + $result = $this->validator->isValid(['custom_layout_update' => 'foo']); + $messages = $this->validator->getMessages(); + self::assertFalse($result); + self::assertSame(['oh no'], $messages); + } +} diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php index 186d1e8e422fd..2c6aa6535c10e 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php @@ -100,16 +100,18 @@ protected function setUp() ->getMock(); $this->uploader = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Uploader::class) - ->setConstructorArgs([ - $this->coreFileStorageDb, - $this->coreFileStorage, - $this->imageFactory, - $this->validator, - $this->filesystem, - $this->readFactory, - null, - $this->random - ]) + ->setConstructorArgs( + [ + $this->coreFileStorageDb, + $this->coreFileStorage, + $this->imageFactory, + $this->validator, + $this->filesystem, + $this->readFactory, + null, + $this->random + ] + ) ->setMethods(['_setUploadFile', 'save', 'getTmpDir', 'checkAllowedExtension']) ->getMock(); } @@ -224,14 +226,16 @@ public function testMoveFileUrlDrivePool($fileUrl, $expectedHost, $expectedDrive ->willReturn($driverMock); $uploaderMock = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Uploader::class) - ->setConstructorArgs([ - $this->coreFileStorageDb, - $this->coreFileStorage, - $this->imageFactory, - $this->validator, - $this->filesystem, - $readFactory, - ]) + ->setConstructorArgs( + [ + $this->coreFileStorageDb, + $this->coreFileStorage, + $this->imageFactory, + $this->validator, + $this->filesystem, + $readFactory, + ] + ) ->getMock(); $result = $uploaderMock->move($fileUrl); diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index 0b7836e432eb5..5cd33ed34d3b0 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "ext-ctype": "*", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", @@ -16,7 +16,8 @@ "magento/module-import-export": "100.3.*", "magento/module-media-storage": "100.3.*", "magento/module-store": "101.0.*", - "magento/module-tax": "100.3.*" + "magento/module-tax": "100.3.*", + "magento/module-authorization": "100.3.*" }, "type": "magento2-module", "license": [ @@ -31,5 +32,5 @@ "Magento\\CatalogImportExport\\": "" } }, - "version": "101.0.2" + "version": "101.0.3" } diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml index 6906272b11d68..4e2fe390e0b17 100644 --- a/app/code/Magento/CatalogImportExport/etc/di.xml +++ b/app/code/Magento/CatalogImportExport/etc/di.xml @@ -25,7 +25,14 @@ <item name="website" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Website</item> <item name="weight" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Weight</item> <item name="quantity" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Quantity</item> + <item name="layout_update" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate</item> + <item name="layout_update_permissions" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdatePermissions</item> </argument> </arguments> </type> + <type name="Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate"> + <arguments> + <argument name="validationState" xsi:type="object">Magento\Framework\Config\ValidationState\Required</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogInventory/Helper/Minsaleqty.php b/app/code/Magento/CatalogInventory/Helper/Minsaleqty.php index b4f5d8b670fb2..96bf5bd965355 100644 --- a/app/code/Magento/CatalogInventory/Helper/Minsaleqty.php +++ b/app/code/Magento/CatalogInventory/Helper/Minsaleqty.php @@ -95,7 +95,7 @@ protected function serializeValue($value) } return $this->serializer->serialize($data); } else { - return ''; + return $value; } } diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php index 6fb0a949941ec..7a46780f2d783 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php @@ -7,8 +7,15 @@ use Magento\Catalog\Model\ProductTypes\ConfigInterface; use Magento\CatalogInventory\Api\StockStateInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\QuoteItemQtyList; +use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Quote\Model\Quote\Item; +/** + * Class StockItem initializes stock item and populates it with data + */ class StockItem { /** @@ -26,26 +33,35 @@ class StockItem */ protected $stockState; + /** + * @var StockStateProviderInterface + */ + private $stockStateProvider; + /** * @param ConfigInterface $typeConfig * @param QuoteItemQtyList $quoteItemQtyList * @param StockStateInterface $stockState + * @param StockStateProviderInterface|null $stockStateProvider */ public function __construct( ConfigInterface $typeConfig, QuoteItemQtyList $quoteItemQtyList, - StockStateInterface $stockState + StockStateInterface $stockState, + StockStateProviderInterface $stockStateProvider = null ) { $this->quoteItemQtyList = $quoteItemQtyList; $this->typeConfig = $typeConfig; $this->stockState = $stockState; + $this->stockStateProvider = $stockStateProvider ?: ObjectManager::getInstance() + ->get(StockStateProviderInterface::class); } /** * Initialize stock item * - * @param \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem - * @param \Magento\Quote\Model\Quote\Item $quoteItem + * @param StockItemInterface $stockItem + * @param Item $quoteItem * @param int $qty * * @return \Magento\Framework\DataObject @@ -54,11 +70,14 @@ public function __construct( * @SuppressWarnings(PHPMD.NPathComplexity) */ public function initialize( - \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem, - \Magento\Quote\Model\Quote\Item $quoteItem, + StockItemInterface $stockItem, + Item $quoteItem, $qty ) { $product = $quoteItem->getProduct(); + $quoteItemId = $quoteItem->getId(); + $quoteId = $quoteItem->getQuoteId(); + $productId = $product->getId(); /** * When we work with subitem */ @@ -68,14 +87,14 @@ public function initialize( * we are using 0 because original qty was processed */ $qtyForCheck = $this->quoteItemQtyList - ->getQty($product->getId(), $quoteItem->getId(), $quoteItem->getQuoteId(), 0); + ->getQty($productId, $quoteItemId, $quoteId, 0); } else { $increaseQty = $quoteItem->getQtyToAdd() ? $quoteItem->getQtyToAdd() : $qty; $rowQty = $qty; $qtyForCheck = $this->quoteItemQtyList->getQty( - $product->getId(), - $quoteItem->getId(), - $quoteItem->getQuoteId(), + $productId, + $quoteItemId, + $quoteId, $increaseQty ); } @@ -90,14 +109,20 @@ public function initialize( $stockItem->setProductName($product->getName()); + /** @var \Magento\Framework\DataObject $result */ $result = $this->stockState->checkQuoteItemQty( - $product->getId(), + $productId, $rowQty, $qtyForCheck, $qty, $product->getStore()->getWebsiteId() ); + /* We need to ensure that any possible plugin will not erase the data */ + $backOrdersQty = $this->stockStateProvider->checkQuoteItemQty($stockItem, $rowQty, $qtyForCheck, $qty) + ->getItemBackorders(); + $result->setItemBackorders($backOrdersQty); + if ($stockItem->hasIsChildItem()) { $stockItem->unsIsChildItem(); } diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php index ecd57baf426c6..81942ff6eca33 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php @@ -322,6 +322,7 @@ protected function _updateIndex($entityIds) /** * Delete records by their ids from index table + * * Used to clean table before re-indexation * * @param array $ids @@ -366,6 +367,8 @@ public function getIdxTable($table = null) } /** + * Get status expression + * * @param AdapterInterface $connection * @param bool $isAggregate * @return mixed @@ -391,6 +394,8 @@ protected function getStatusExpression(AdapterInterface $connection, $isAggregat } /** + * Get stock configuration + * * @return StockConfigurationInterface * * @deprecated 100.1.0 @@ -406,6 +411,8 @@ protected function getStockConfiguration() } /** + * Get query processor composite + * * @return QueryProcessorComposite */ private function getQueryProcessorComposite() diff --git a/app/code/Magento/CatalogInventory/Observer/ParentItemProcessorInterface.php b/app/code/Magento/CatalogInventory/Observer/ParentItemProcessorInterface.php new file mode 100644 index 0000000000000..dd5689a396ebd --- /dev/null +++ b/app/code/Magento/CatalogInventory/Observer/ParentItemProcessorInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Observer; + +use Magento\Catalog\Api\Data\ProductInterface as Product; + +/** + * Interface for processing parent items of complex product types + */ +interface ParentItemProcessorInterface +{ + /** + * Process stock for parent items + * + * @param Product $product + * @return void + */ + public function process(Product $product); +} diff --git a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php index 03ba58d3f4987..dd67140fa0c18 100644 --- a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php @@ -13,6 +13,8 @@ use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\CatalogInventory\Model\StockItemValidator; use Magento\Framework\Event\Observer as EventObserver; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; /** * Saves stock data from a product to the Stock Item @@ -39,6 +41,11 @@ class SaveInventoryDataObserver implements ObserverInterface */ private $stockItemValidator; + /** + * @var ParentItemProcessorInterface[] + */ + private $parentItemProcessorPool; + /** * @var array */ @@ -77,15 +84,18 @@ class SaveInventoryDataObserver implements ObserverInterface * @param StockConfigurationInterface $stockConfiguration * @param StockRegistryInterface $stockRegistry * @param StockItemValidator $stockItemValidator + * @param ParentItemProcessorInterface[] $parentItemProcessorPool */ public function __construct( StockConfigurationInterface $stockConfiguration, StockRegistryInterface $stockRegistry, - StockItemValidator $stockItemValidator = null + StockItemValidator $stockItemValidator = null, + array $parentItemProcessorPool = [] ) { $this->stockConfiguration = $stockConfiguration; $this->stockRegistry = $stockRegistry; $this->stockItemValidator = $stockItemValidator ?: ObjectManager::getInstance()->get(StockItemValidator::class); + $this->parentItemProcessorPool = $parentItemProcessorPool; } /** @@ -96,10 +106,15 @@ public function __construct( * * @param EventObserver $observer * @return void + * @throws LocalizedException + * @throws NoSuchEntityException */ public function execute(EventObserver $observer) { + /** @var Product $product */ $product = $observer->getEvent()->getProduct(); + + /** @var Item $stockItem */ $stockItem = $this->getStockItemToBeUpdated($product); if ($product->getStockData() !== null) { @@ -108,6 +123,7 @@ public function execute(EventObserver $observer) } $this->stockItemValidator->validate($product, $stockItem); $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); + $this->processParents($product); } /** @@ -156,4 +172,17 @@ private function getStockData(Product $product) } return $stockData; } + + /** + * Process stock data for parent products + * + * @param Product $product + * @return void + */ + private function processParents(Product $product) + { + foreach ($this->parentItemProcessorPool as $processor) { + $processor->process($product); + } + } } diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml new file mode 100644 index 0000000000000..30fddba4241b7 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/StorefrontAssertProductStockStatusActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckProductStockStatus"> + <arguments> + <argument name="productUrlKey" type="string"/> + <argument name="productName" type="string"/> + <argument name="stockStatus" type="string" defaultValue="IN STOCK"/> + </arguments> + <amOnPage url="{{StorefrontProductPage.url(productUrlKey)}}" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{productName}}" stepKey="seeProductName"/> + <see userInput="{{stockStatus}}" stepKey="assertProductStockStatus"/> + </actionGroup> +</actionGroups> + \ No newline at end of file diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml new file mode 100644 index 0000000000000..e14c36446fc2b --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="StockOptionsDisplayOutOfStockProductsEnable"> + <data key="path">cataloginventory/options/show_out_of_stock</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StockOptionsDisplayOutOfStockProductsDisable"> + <data key="path">cataloginventory/options/show_out_of_stock</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml new file mode 100644 index 0000000000000..3a49b821ead5f --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableCatalogInventoryConfigData"> + <!--Default Value --> + <data key="path">cataloginventory/options/can_subtract</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableCatalogInventoryConfigData"> + <data key="path">cataloginventory/options/can_subtract</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml index 706df79b1ef8b..8458fcf3b94e0 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml @@ -122,8 +122,11 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoice"/> + <waitForPageLoad stepKey="waitForNewInvoicePageToLoad"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <waitForPageLoad stepKey="waitForNewInvoiceToBeCreated"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage"/> <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShip"/> <waitForLoadingMaskToDisappear stepKey="waitForShipLoadingMask"/> diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Helper/MinsaleqtyTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Helper/MinsaleqtyTest.php index f008ed7d9d694..051e002b80c3e 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Helper/MinsaleqtyTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Helper/MinsaleqtyTest.php @@ -216,7 +216,7 @@ public function testMakeStorableArrayFieldValue($value, $result, $serializeCallC public function makeStorableArrayFieldValueDataProvider() { return [ - 'invalid bool' => [false, ''], + 'invalid bool' => [false, false], 'invalid empty string' => ['', ''], 'valid numeric' => ['22', '22'], 'valid empty array' => [[], '[]', 1], @@ -240,7 +240,8 @@ public function makeStorableArrayFieldValueDataProvider() '[1]', 1, [0 => 1.0] - ] + ], + 'json value' => ['{"32000":2,"0":1}', '{"32000":2,"0":1}'], ]; } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php index 8c9a1aa7715ec..01dab7fce3323 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php @@ -8,6 +8,7 @@ use Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\QuoteItemQtyList; /** + * Class StockItemTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class StockItemTest extends \PHPUnit\Framework\TestCase @@ -28,10 +29,18 @@ class StockItemTest extends \PHPUnit\Framework\TestCase protected $typeConfig; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\CatalogInventory\Api\StockStateInterface\PHPUnit_Framework_MockObject_MockObject */ protected $stockStateMock; + /** + * @var \Magento\CatalogInventory\Model\StockStateProviderInterface| \PHPUnit_Framework_MockObject_MockObject + */ + private $stockStateProviderMock; + + /** + * @inheritdoc + */ protected function setUp() { $this->quoteItemQtyList = $this @@ -48,17 +57,25 @@ protected function setUp() $this->stockStateMock = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockStateInterface::class) ->disableOriginalConstructor() ->getMock(); + + $this->stockStateProviderMock = $this + ->getMockBuilder(\Magento\CatalogInventory\Model\StockStateProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = $objectManagerHelper->getObject( \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\StockItem::class, [ 'quoteItemQtyList' => $this->quoteItemQtyList, 'typeConfig' => $this->typeConfig, - 'stockState' => $this->stockStateMock + 'stockState' => $this->stockStateMock, + 'stockStateProvider' => $this->stockStateProviderMock ] ); } /** + * Test initialize with Subitem * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testInitializeWithSubitem() @@ -141,6 +158,10 @@ public function testInitializeWithSubitem() ->method('checkQuoteItemQty') ->withAnyParameters() ->will($this->returnValue($result)); + $this->stockStateProviderMock->expects($this->once()) + ->method('checkQuoteItemQty') + ->withAnyParameters() + ->will($this->returnValue($result)); $product->expects($this->once()) ->method('getCustomOption') ->with('product_type') @@ -177,13 +198,16 @@ public function testInitializeWithSubitem() $quoteItem->expects($this->once())->method('setUseOldQty')->with('item')->will($this->returnSelf()); $result->expects($this->exactly(2))->method('getMessage')->will($this->returnValue('message')); $quoteItem->expects($this->once())->method('setMessage')->with('message')->will($this->returnSelf()); - $result->expects($this->exactly(2))->method('getItemBackorders')->will($this->returnValue('backorders')); + $result->expects($this->exactly(3))->method('getItemBackorders')->will($this->returnValue('backorders')); $quoteItem->expects($this->once())->method('setBackorders')->with('backorders')->will($this->returnSelf()); $quoteItem->expects($this->once())->method('setStockStateResult')->with($result)->will($this->returnSelf()); $this->model->initialize($stockItem, $quoteItem, $qty); } + /** + * Test initialize without Subitem + */ public function testInitializeWithoutSubitem() { $qty = 3; @@ -234,6 +258,10 @@ public function testInitializeWithoutSubitem() ->with($productId, 'quote_item_id', 'quote_id', $qty) ->will($this->returnValue('summary_qty')); $this->stockStateMock->expects($this->once()) + ->method('checkQuoteItemQty') + ->withAnyParameters() + ->will($this->returnValue($result)); + $this->stockStateProviderMock->expects($this->once()) ->method('checkQuoteItemQty') ->withAnyParameters() ->will($this->returnValue($result)); @@ -256,7 +284,7 @@ public function testInitializeWithoutSubitem() $result->expects($this->once())->method('getHasQtyOptionUpdate')->will($this->returnValue(false)); $result->expects($this->once())->method('getItemUseOldQty')->will($this->returnValue(null)); $result->expects($this->once())->method('getMessage')->will($this->returnValue(null)); - $result->expects($this->once())->method('getItemBackorders')->will($this->returnValue(null)); + $result->expects($this->exactly(2))->method('getItemBackorders')->will($this->returnValue(null)); $this->model->initialize($stockItem, $quoteItem, $qty); } diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 579a3ee4c249e..b1f43b996e537 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-config": "101.1.*", @@ -29,5 +29,5 @@ } }, "abandoned": "magento/inventory-composer-metapackage", - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogInventory/etc/db_schema.xml b/app/code/Magento/CatalogInventory/etc/db_schema.xml index 5ac7fedc5aa18..67a200eb37125 100644 --- a/app/code/Magento/CatalogInventory/etc/db_schema.xml +++ b/app/code/Magento/CatalogInventory/etc/db_schema.xml @@ -142,7 +142,7 @@ <column name="website_id"/> </index> </table> - <table name="cataloginventory_stock_status_tmp" resource="default" engine="memory" + <table name="cataloginventory_stock_status_tmp" resource="default" engine="innodb" comment="Cataloginventory Stock Status Indexer Tmp"> <column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Product Id"/> @@ -159,10 +159,10 @@ <column name="website_id"/> <column name="stock_id"/> </constraint> - <index referenceId="CATALOGINVENTORY_STOCK_STATUS_TMP_STOCK_ID" indexType="hash"> + <index referenceId="CATALOGINVENTORY_STOCK_STATUS_TMP_STOCK_ID" indexType="btree"> <column name="stock_id"/> </index> - <index referenceId="CATALOGINVENTORY_STOCK_STATUS_TMP_WEBSITE_ID" indexType="hash"> + <index referenceId="CATALOGINVENTORY_STOCK_STATUS_TMP_WEBSITE_ID" indexType="btree"> <column name="website_id"/> </index> </table> diff --git a/app/code/Magento/CatalogInventory/view/frontend/templates/qtyincrements.phtml b/app/code/Magento/CatalogInventory/view/frontend/templates/qtyincrements.phtml index 8e0dbf1278ed2..8b63d29be8154 100644 --- a/app/code/Magento/CatalogInventory/view/frontend/templates/qtyincrements.phtml +++ b/app/code/Magento/CatalogInventory/view/frontend/templates/qtyincrements.phtml @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var $block \Magento\CatalogInventory\Block\Qtyincrements */ ?> <?php if ($block->getProductQtyIncrements()) : ?> <div class="product pricing"> - <?= /* @escapeNotVerified */ __('%1 is available to buy in increments of %2', $block->getProductName(), $block->getProductQtyIncrements()) ?> + <?= $block->escapeHtml(__('%1 is available to buy in increments of %2', $block->getProductName(), $block->getProductQtyIncrements())) ?> </div> <?php endif ?> diff --git a/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/composite.phtml b/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/composite.phtml index 481ed1297a801..de667d19fadb0 100644 --- a/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/composite.phtml +++ b/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/composite.phtml @@ -4,30 +4,28 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var $block \Magento\CatalogInventory\Block\Stockqty\Composite */ ?> -<?php if ($block->isMsgVisible()): ?> +<?php if ($block->isMsgVisible()) : ?> <div class="availability only"> <a href="#" - data-mage-init='{"toggleAdvanced": {"selectorsToggleClass": "active", "baseToggleClass": "expanded", "toggleContainers": "#<?= /* @escapeNotVerified */ $block->getDetailsPlaceholderId() ?>"}}' - id="<?= /* @escapeNotVerified */ $block->getPlaceholderId() ?>" - title="<?= /* @escapeNotVerified */ __('Only %1 left', ($block->getStockQtyLeft())) ?>" + data-mage-init='{"toggleAdvanced": {"selectorsToggleClass": "active", "baseToggleClass": "expanded", "toggleContainers": "#<?= $block->escapeHtmlAttr($block->getDetailsPlaceholderId()) ?>"}}' + id="<?= $block->escapeHtmlAttr($block->getPlaceholderId()) ?>" + title="<?= $block->escapeHtmlAttr(__('Only %1 left', ($block->getStockQtyLeft()))) ?>" class="action show"> - <?= /* @escapeNotVerified */ __('Only %1 left', "<strong>{$block->getStockQtyLeft()}</strong>") ?> + <?= /* @noEscape */ __('Only %1 left', "<strong>{$block->escapeHtml($block->getStockQtyLeft())}</strong>") ?> </a> </div> - <div class="availability only detailed" id="<?= /* @escapeNotVerified */ $block->getDetailsPlaceholderId() ?>"> + <div class="availability only detailed" id="<?= $block->escapeHtmlAttr($block->getDetailsPlaceholderId()) ?>"> <div class="table-wrapper"> <table class="data table"> - <caption class="table-caption"><?= /* @escapeNotVerified */ __('Product availability') ?></caption> + <caption class="table-caption"><?= $block->escapeHtml(__('Product availability')) ?></caption> <thead> <tr> - <th class="col item" scope="col"><?= /* @escapeNotVerified */ __('Product Name') ?></th> - <th class="col qty" scope="col"><?= /* @escapeNotVerified */ __('Qty') ?></th> + <th class="col item" scope="col"><?= $block->escapeHtml(__('Product Name')) ?></th> + <th class="col qty" scope="col"><?= $block->escapeHtml(__('Qty')) ?></th> </tr> </thead> <tbody> @@ -35,8 +33,8 @@ <?php $childProductStockQty = $block->getProductStockQty($childProduct); ?> <?php if ($childProductStockQty > 0) : ?> <tr> - <td data-th="<?= $block->escapeHtml(__('Product Name')) ?>" class="col item"><?= /* @escapeNotVerified */ $childProduct->getName() ?></td> - <td data-th="<?= $block->escapeHtml(__('Qty')) ?>" class="col qty"><?= /* @escapeNotVerified */ $childProductStockQty ?></td> + <td data-th="<?= $block->escapeHtmlAttr(__('Product Name')) ?>" class="col item"><?= $block->escapeHtml($childProduct->getName()) ?></td> + <td data-th="<?= $block->escapeHtmlAttr(__('Qty')) ?>" class="col qty"><?= $block->escapeHtml($childProductStockQty) ?></td> </tr> <?php endif ?> <?php endforeach ?> diff --git a/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/default.phtml b/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/default.phtml index 43fb697de2621..c32cb9dd6ecda 100644 --- a/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/default.phtml +++ b/app/code/Magento/CatalogInventory/view/frontend/templates/stockqty/default.phtml @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var $block \Magento\CatalogInventory\Block\Stockqty\DefaultStockqty */ ?> -<?php if ($block->isMsgVisible()): ?> - <div class="availability only" title="<?= /* @escapeNotVerified */ __('Only %1 left', ($block->getStockQtyLeft())) ?>"> - <?= /* @escapeNotVerified */ __('Only %1 left', "<strong>{$block->getStockQtyLeft()}</strong>") ?> +<?php if ($block->isMsgVisible()) : ?> + <div class="availability only" title="<?= $block->escapeHtmlAttr(__('Only %1 left', ($block->getStockQtyLeft()))) ?>"> + <?= /* @noEscape */ __('Only %1 left', "<strong>{$block->escapeHtml($block->getStockQtyLeft())}</strong>") ?> </div> <?php endif ?> diff --git a/app/code/Magento/CatalogInventoryGraphQl/composer.json b/app/code/Magento/CatalogInventoryGraphQl/composer.json index 32ef30e873422..6f0ebc97c4c17 100644 --- a/app/code/Magento/CatalogInventoryGraphQl/composer.json +++ b/app/code/Magento/CatalogInventoryGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-store": "101.0.*", "magento/module-catalog": "103.0.*", @@ -21,5 +21,5 @@ "Magento\\CatalogInventoryGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php index 98b33e3d5131b..421a6e718b3dc 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php @@ -7,6 +7,7 @@ namespace Magento\CatalogRule\Model\Indexer; use Magento\Catalog\Model\Product; +use Magento\CatalogRule\Model\ResourceModel\Rule\Collection as RuleCollection; use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory as RuleCollectionFactory; use Magento\CatalogRule\Model\Rule; use Magento\Framework\App\ObjectManager; @@ -15,6 +16,8 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; /** + * Catalog rule index builder + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -270,14 +273,14 @@ public function reindexByIds(array $ids) */ protected function doReindexByIds($ids) { - $this->cleanByIds($ids); + $this->cleanProductIndex($ids); $products = $this->productLoader->getProducts($ids); - foreach ($this->getActiveRules() as $rule) { - foreach ($products as $product) { - $this->applyRule($rule, $product); - } + $activeRules = $this->getActiveRules(); + foreach ($products as $product) { + $this->applyRules($activeRules, $product); } + $this->reindexRuleGroupWebsite->execute(); } /** @@ -322,6 +325,30 @@ protected function doReindexFull() ); } + /** + * Clean product index + * + * @param array $productIds + * @return void + */ + private function cleanProductIndex(array $productIds): void + { + $where = ['product_id IN (?)' => $productIds]; + $this->connection->delete($this->getTable('catalogrule_product'), $where); + } + + /** + * Clean product price index + * + * @param array $productIds + * @return void + */ + private function cleanProductPriceIndex(array $productIds): void + { + $where = ['product_id IN (?)' => $productIds]; + $this->connection->delete($this->getTable('catalogrule_product_price'), $where); + } + /** * Clean by product ids * @@ -330,51 +357,35 @@ protected function doReindexFull() */ protected function cleanByIds($productIds) { - $query = $this->connection->deleteFromSelect( - $this->connection - ->select() - ->from($this->resource->getTableName('catalogrule_product'), 'product_id') - ->distinct() - ->where('product_id IN (?)', $productIds), - $this->resource->getTableName('catalogrule_product') - ); - $this->connection->query($query); - - $query = $this->connection->deleteFromSelect( - $this->connection->select() - ->from($this->resource->getTableName('catalogrule_product_price'), 'product_id') - ->distinct() - ->where('product_id IN (?)', $productIds), - $this->resource->getTableName('catalogrule_product_price') - ); - $this->connection->query($query); + $this->cleanProductIndex($productIds); + $this->cleanProductPriceIndex($productIds); } /** + * Assign product to rule + * * @param Rule $rule * @param Product $product - * @return $this - * @throws \Exception - * @SuppressWarnings(PHPMD.NPathComplexity) + * @return void */ - protected function applyRule(Rule $rule, $product) + private function assignProductToRule(Rule $rule, Product $product): void { - $ruleId = $rule->getId(); - $productEntityId = $product->getId(); - $websiteIds = array_intersect($product->getWebsiteIds(), $rule->getWebsiteIds()); - if (!$rule->validate($product)) { - return $this; + return; } + $ruleId = (int) $rule->getId(); + $productEntityId = (int) $product->getId(); + $ruleProductTable = $this->getTable('catalogrule_product'); $this->connection->delete( - $this->resource->getTableName('catalogrule_product'), + $ruleProductTable, [ - $this->connection->quoteInto('rule_id = ?', $ruleId), - $this->connection->quoteInto('product_id = ?', $productEntityId) + 'rule_id = ?' => $ruleId, + 'product_id = ?' => $productEntityId, ] ); + $websiteIds = array_intersect($product->getWebsiteIds(), $rule->getWebsiteIds()); $customerGroupIds = $rule->getCustomerGroupIds(); $fromTime = strtotime($rule->getFromDate()); $toTime = strtotime($rule->getToDate()); @@ -385,36 +396,44 @@ protected function applyRule(Rule $rule, $product) $actionStop = $rule->getStopRulesProcessing(); $rows = []; - try { - foreach ($websiteIds as $websiteId) { - foreach ($customerGroupIds as $customerGroupId) { - $rows[] = [ - 'rule_id' => $ruleId, - 'from_time' => $fromTime, - 'to_time' => $toTime, - 'website_id' => $websiteId, - 'customer_group_id' => $customerGroupId, - 'product_id' => $productEntityId, - 'action_operator' => $actionOperator, - 'action_amount' => $actionAmount, - 'action_stop' => $actionStop, - 'sort_order' => $sortOrder, - ]; - - if (count($rows) == $this->batchCount) { - $this->connection->insertMultiple($this->getTable('catalogrule_product'), $rows); - $rows = []; - } + foreach ($websiteIds as $websiteId) { + foreach ($customerGroupIds as $customerGroupId) { + $rows[] = [ + 'rule_id' => $ruleId, + 'from_time' => $fromTime, + 'to_time' => $toTime, + 'website_id' => $websiteId, + 'customer_group_id' => $customerGroupId, + 'product_id' => $productEntityId, + 'action_operator' => $actionOperator, + 'action_amount' => $actionAmount, + 'action_stop' => $actionStop, + 'sort_order' => $sortOrder, + ]; + + if (count($rows) == $this->batchCount) { + $this->connection->insertMultiple($ruleProductTable, $rows); + $rows = []; } } - - if (!empty($rows)) { - $this->connection->insertMultiple($this->resource->getTableName('catalogrule_product'), $rows); - } - } catch (\Exception $e) { - throw $e; } + if ($rows) { + $this->connection->insertMultiple($ruleProductTable, $rows); + } + } + /** + * Apply rule + * + * @param Rule $rule + * @param Product $product + * @return $this + * @throws \Exception + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function applyRule(Rule $rule, $product) + { + $this->assignProductToRule($rule, $product); $this->reindexRuleProductPrice->execute($this->batchCount, $product); $this->reindexRuleGroupWebsite->execute(); @@ -422,6 +441,25 @@ protected function applyRule(Rule $rule, $product) } /** + * Apply rules + * + * @param RuleCollection $ruleCollection + * @param Product $product + * @return void + */ + private function applyRules(RuleCollection $ruleCollection, Product $product): void + { + foreach ($ruleCollection as $rule) { + $this->assignProductToRule($rule, $product); + } + + $this->cleanProductPriceIndex([$product->getId()]); + $this->reindexRuleProductPrice->execute($this->batchCount, $product); + } + + /** + * Retrieve table name + * * @param string $tableName * @return string */ @@ -431,6 +469,8 @@ protected function getTable($tableName) } /** + * Update rule product data + * * @param Rule $rule * @return $this * @deprecated 101.0.0 @@ -456,6 +496,8 @@ protected function updateRuleProductData(Rule $rule) } /** + * Apply all rules + * * @param Product|null $product * @throws \Exception * @return $this @@ -495,8 +537,10 @@ protected function deleteOldData() } /** + * Calculate rule product price + * * @param array $ruleData - * @param null $productData + * @param array $productData * @return float * @deprecated 101.0.0 * @see ProductPriceCalculator::calculate @@ -507,6 +551,8 @@ protected function calcRuleProductPrice($ruleData, $productData = null) } /** + * Get rule products statement + * * @param int $websiteId * @param Product|null $product * @return \Zend_Db_Statement_Interface @@ -520,6 +566,8 @@ protected function getRuleProductsStmt($websiteId, Product $product = null) } /** + * Save rule product prices + * * @param array $arrData * @return $this * @throws \Exception @@ -535,7 +583,7 @@ protected function saveRuleProductPrices($arrData) /** * Get active rules * - * @return array + * @return RuleCollection */ protected function getActiveRules() { @@ -545,7 +593,7 @@ protected function getActiveRules() /** * Get active rules * - * @return array + * @return RuleCollection */ protected function getAllRules() { @@ -553,6 +601,8 @@ protected function getAllRules() } /** + * Get product + * * @param int $productId * @return Product */ @@ -565,6 +615,8 @@ protected function getProduct($productId) } /** + * Log critical exception + * * @param \Exception $e * @return void */ diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 55a234bb8ae27..e589c8595ce2c 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -8,7 +8,10 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; -use Magento\Framework\App\ObjectManager; +use Magento\CatalogRule\Model\Rule; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; /** * Reindex rule relations with products. @@ -16,7 +19,7 @@ class ReindexRuleProduct { /** - * @var \Magento\Framework\App\ResourceConnection + * @var ResourceConnection */ private $resource; @@ -31,36 +34,40 @@ class ReindexRuleProduct private $tableSwapper; /** - * @param \Magento\Framework\App\ResourceConnection $resource + * @var TimezoneInterface + */ + private $localeDate; + + /** + * @param ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher - * @param TableSwapper|null $tableSwapper + * @param TableSwapper $tableSwapper + * @param TimezoneInterface $localeDate */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, + ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, - TableSwapper $tableSwapper = null + TableSwapper $tableSwapper, + TimezoneInterface $localeDate ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; - $this->tableSwapper = $tableSwapper ?? - ObjectManager::getInstance()->get(TableSwapper::class); + $this->tableSwapper = $tableSwapper; + $this->localeDate = $localeDate; } /** * Reindex information about rule relations with products. * - * @param \Magento\CatalogRule\Model\Rule $rule + * @param Rule $rule * @param int $batchCount * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function execute( - \Magento\CatalogRule\Model\Rule $rule, - $batchCount, - $useAdditionalTable = false - ) { + public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) + { if (!$rule->getIsActive() || empty($rule->getWebsiteIds())) { return false; } @@ -84,21 +91,26 @@ public function execute( $ruleId = $rule->getId(); $customerGroupIds = $rule->getCustomerGroupIds(); - $fromTime = strtotime($rule->getFromDate()); - $toTime = strtotime($rule->getToDate()); - $toTime = $toTime ? $toTime + \Magento\CatalogRule\Model\Indexer\IndexBuilder::SECONDS_IN_DAY - 1 : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); $actionStop = $rule->getStopRulesProcessing(); $rows = []; + foreach ($websiteIds as $websiteId) { + $scopeTz = new \DateTimeZone( + $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId) + ); + $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $toTime = $rule->getToDate() + ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 + : 0; - foreach ($productIds as $productId => $validationByWebsite) { - foreach ($websiteIds as $websiteId) { + foreach ($productIds as $productId => $validationByWebsite) { if (empty($validationByWebsite[$websiteId])) { continue; } + foreach ($customerGroupIds as $customerGroupId) { $rows[] = [ 'rule_id' => $ruleId, @@ -123,6 +135,7 @@ public function execute( if (!empty($rows)) { $connection->insertMultiple($indexTable, $rows); } + return true; } } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index 6a87be3c50a64..11ba87730bec1 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -6,54 +6,58 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\Catalog\Model\Product; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\StoreManagerInterface; + /** * Reindex product prices according rule settings. */ class ReindexRuleProductPrice { /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder + * @var RuleProductsSelectBuilder */ private $ruleProductsSelectBuilder; /** - * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator + * @var ProductPriceCalculator */ private $productPriceCalculator; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var TimezoneInterface */ - private $dateTime; + private $localeDate; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor + * @var RuleProductPricesPersistor */ private $pricesPersistor; /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime - * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + * @param TimezoneInterface $localeDate + * @param RuleProductPricesPersistor $pricesPersistor */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, - \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + StoreManagerInterface $storeManager, + RuleProductsSelectBuilder $ruleProductsSelectBuilder, + ProductPriceCalculator $productPriceCalculator, + TimezoneInterface $localeDate, + RuleProductPricesPersistor $pricesPersistor ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; $this->productPriceCalculator = $productPriceCalculator; - $this->dateTime = $dateTime; + $this->localeDate = $localeDate; $this->pricesPersistor = $pricesPersistor; } @@ -61,22 +65,16 @@ public function __construct( * Reindex product prices. * * @param int $batchCount - * @param \Magento\Catalog\Model\Product|null $product + * @param Product|null $product * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function execute( - $batchCount, - \Magento\Catalog\Model\Product $product = null, - $useAdditionalTable = false - ) { - $fromDate = mktime(0, 0, 0, date('m'), date('d') - 1); - $toDate = mktime(0, 0, 0, date('m'), date('d') + 1); - + public function execute($batchCount, Product $product = null, $useAdditionalTable = false) + { /** * Update products rules prices per each website separately - * because of max join limit in mysql + * because for each website date in website's timezone should be used */ foreach ($this->storeManager->getWebsites() as $website) { $productsStmt = $this->ruleProductsSelectBuilder->build($website->getId(), $product, $useAdditionalTable); @@ -84,6 +82,13 @@ public function execute( $stopFlags = []; $prevKey = null; + $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); + $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true); + $previousDate = (clone $currentDate)->modify('-1 day'); + $previousDate->setTime(23, 59, 59); + $nextDate = (clone $currentDate)->modify('+1 day'); + $nextDate->setTime(0, 0, 0); + while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; $productKey = $ruleProductId . @@ -100,12 +105,11 @@ public function execute( } } - $ruleData['from_time'] = $this->roundTime($ruleData['from_time']); - $ruleData['to_time'] = $this->roundTime($ruleData['to_time']); /** * Build prices for each day */ - for ($time = $fromDate; $time <= $toDate; $time += IndexBuilder::SECONDS_IN_DAY) { + foreach ([$previousDate, $currentDate, $nextDate] as $date) { + $time = $date->getTimestamp(); if (($ruleData['from_time'] == 0 || $time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 || $time <= $ruleData['to_time']) @@ -118,7 +122,7 @@ public function execute( if (!isset($dayPrices[$priceKey])) { $dayPrices[$priceKey] = [ - 'rule_date' => $time, + 'rule_date' => $date, 'website_id' => $ruleData['website_id'], 'customer_group_id' => $ruleData['customer_group_id'], 'product_id' => $ruleProductId, @@ -151,18 +155,7 @@ public function execute( } $this->pricesPersistor->execute($dayPrices, $useAdditionalTable); } - return true; - } - /** - * @param int $timeStamp - * @return int - */ - private function roundTime($timeStamp) - { - if (is_numeric($timeStamp) && $timeStamp != 0) { - $timeStamp = $this->dateTime->timestamp($this->dateTime->date('Y-m-d 00:00:00', $timeStamp)); - } - return $timeStamp; + return true; } } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php index 0b1264a216257..25bcfb8f20e5f 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php @@ -76,25 +76,19 @@ public function execute(array $priceData, $useAdditionalTable = false) ); } - $productIds = []; - - try { - foreach ($priceData as $key => $data) { - $productIds['product_id'] = $data['product_id']; - $priceData[$key]['rule_date'] = $this->dateFormat->formatDate($data['rule_date'], false); - $priceData[$key]['latest_start_date'] = $this->dateFormat->formatDate( - $data['latest_start_date'], - false - ); - $priceData[$key]['earliest_end_date'] = $this->dateFormat->formatDate( - $data['earliest_end_date'], - false - ); - } - $connection->insertOnDuplicate($indexTable, $priceData); - } catch (\Exception $e) { - throw $e; + foreach ($priceData as $key => $data) { + $priceData[$key]['rule_date'] = $this->dateFormat->formatDate($data['rule_date'], false); + $priceData[$key]['latest_start_date'] = $this->dateFormat->formatDate( + $data['latest_start_date'], + false + ); + $priceData[$key]['earliest_end_date'] = $this->dateFormat->formatDate( + $data['earliest_end_date'], + false + ); } + $connection->insertOnDuplicate($indexTable, $priceData); + return true; } } diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php index 0ea31f5dbafb2..1fd6f0cbc986f 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php @@ -4,6 +4,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogRule\Model\ResourceModel\Product; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; @@ -11,6 +13,8 @@ /** * Add catalog rule prices to collection + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class CollectionProcessor { @@ -61,6 +65,8 @@ public function __construct( } /** + * Join prices to collection + * * @param ProductCollection $productCollection * @param string $joinColumn * @return ProductCollection @@ -73,18 +79,21 @@ public function addPriceData(ProductCollection $productCollection, $joinColumn = $productCollection->getSelect() ->joinLeft( ['catalog_rule' => $this->resource->getTableName('catalogrule_product_price')], - implode(' AND ', [ - 'catalog_rule.product_id = ' . $connection->quoteIdentifier($joinColumn), - $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()), - $connection->quoteInto( - 'catalog_rule.customer_group_id = ?', - $this->customerSession->getCustomerGroupId() - ), - $connection->quoteInto( - 'catalog_rule.rule_date = ?', - $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false) - ), - ]), + implode( + ' AND ', + [ + 'catalog_rule.product_id = ' . $connection->quoteIdentifier($joinColumn), + $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()), + $connection->quoteInto( + 'catalog_rule.customer_group_id = ?', + $this->customerSession->getCustomerGroupId() + ), + $connection->quoteInto( + 'catalog_rule.rule_date = ?', + $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false) + ), + ] + ), [CatalogRulePrice::PRICE_CODE => 'rule_price'] ); $productCollection->setFlag('catalog_rule_loaded', true); diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 3f396cacd37da..48c463fc18b80 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogRule\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; @@ -11,6 +13,11 @@ use Magento\Framework\DB\Select; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; +/** + * Provide Select object for retrieve product id with minimal price + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelectBuilderInterface { /** @@ -77,7 +84,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function build($productId) { diff --git a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php index 75a208e87100b..bf0c85e671dd7 100644 --- a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php +++ b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php @@ -7,19 +7,23 @@ /** * Catalog Price rules observer model */ +declare(strict_types=1); + namespace Magento\CatalogRule\Observer; use Magento\Catalog\Model\Product; -use Magento\CatalogRule\Model\Rule; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Customer\Model\Session as CustomerModelSession; -use Magento\Framework\Event\Observer as EventObserver; use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\Event\ObserverInterface; /** + * Observer for applying catalog rules on product collection + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class PrepareCatalogProductCollectionPricesObserver implements ObserverInterface { @@ -85,7 +89,7 @@ public function __construct( */ public function execute(\Magento\Framework\Event\Observer $observer) { - /* @var $collection ProductCollection */ + /** @var ProductCollection $collection */ $collection = $observer->getEvent()->getCollection(); $store = $this->storeManager->getStore($observer->getEvent()->getStoreId()); $websiteId = $store->getWebsiteId(); diff --git a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php index 2dce2cb2f5b1c..89ed519cfb8c8 100644 --- a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php +++ b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php @@ -3,20 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * Catalog Price rules observer model - */ namespace Magento\CatalogRule\Observer; -use Magento\Catalog\Model\Product; -use Magento\CatalogRule\Model\Rule; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Customer\Model\Session as CustomerModelSession; -use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Registry; use Magento\Framework\Event\ObserverInterface; +/** + * Observer for applying catalog rules on product for admin area + */ class ProcessAdminFinalPriceObserver implements ObserverInterface { /** diff --git a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php index 2d4042f691502..075fe9e51f7dc 100644 --- a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php +++ b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php @@ -3,20 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * Catalog Price rules observer model - */ namespace Magento\CatalogRule\Observer; use Magento\Framework\Event\ObserverInterface; -use Magento\Catalog\Model\Product; -use Magento\CatalogRule\Model\Rule; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Customer\Model\Session as CustomerModelSession; -use Magento\Framework\Event\Observer as EventObserver; +/** + * Observer for applying catalog rules on product for frontend area + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class ProcessFrontFinalPriceObserver implements ObserverInterface { /** diff --git a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php index e4efd044dc15d..7cbbc547571ab 100644 --- a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php @@ -3,22 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogRule\Pricing\Price; use Magento\Catalog\Model\Product; use Magento\CatalogRule\Model\ResourceModel\Rule; -use Magento\CatalogRule\Model\ResourceModel\RuleFactory; use Magento\Customer\Model\Session; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\Adjustment\Calculator; use Magento\Framework\Pricing\Price\AbstractPrice; use Magento\Framework\Pricing\Price\BasePriceProviderInterface; +use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Store\Model\StoreManager; +use Magento\Store\Model\StoreManagerInterface; /** * Class CatalogRulePrice + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterface { @@ -28,28 +30,22 @@ class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterfa const PRICE_CODE = 'catalog_rule_price'; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ protected $dateTime; /** - * @var \Magento\Store\Model\StoreManager + * @var StoreManagerInterface */ protected $storeManager; /** - * @var \Magento\Customer\Model\Session + * @var Session */ protected $customerSession; /** - * @var \Magento\CatalogRule\Model\ResourceModel\RuleFactory - * @deprecated 100.0.2 - */ - protected $resourceRuleFactory; - - /** - * @var \Magento\CatalogRule\Model\ResourceModel\Rule + * @var Rule */ private $ruleResource; @@ -57,27 +53,27 @@ class CatalogRulePrice extends AbstractPrice implements BasePriceProviderInterfa * @param Product $saleableItem * @param float $quantity * @param Calculator $calculator - * @param RuleFactory $catalogRuleResourceFactory + * @param PriceCurrencyInterface $priceCurrency * @param TimezoneInterface $dateTime - * @param StoreManager $storeManager + * @param StoreManagerInterface $storeManager * @param Session $customerSession - * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param Rule $ruleResource */ public function __construct( Product $saleableItem, $quantity, Calculator $calculator, - \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + PriceCurrencyInterface $priceCurrency, TimezoneInterface $dateTime, - StoreManager $storeManager, + StoreManagerInterface $storeManager, Session $customerSession, - RuleFactory $catalogRuleResourceFactory + Rule $ruleResource ) { parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); $this->dateTime = $dateTime; $this->storeManager = $storeManager; $this->customerSession = $customerSession; - $this->resourceRuleFactory = $catalogRuleResourceFactory; + $this->ruleResource = $ruleResource; } /** @@ -91,13 +87,12 @@ public function getValue() if ($this->product->hasData(self::PRICE_CODE)) { $this->value = (float)$this->product->getData(self::PRICE_CODE) ?: false; } else { - $this->value = $this->getRuleResource() - ->getRulePrice( - $this->dateTime->scopeDate($this->storeManager->getStore()->getId()), - $this->storeManager->getStore()->getWebsiteId(), - $this->customerSession->getCustomerGroupId(), - $this->product->getId() - ); + $this->value = $this->ruleResource->getRulePrice( + $this->dateTime->scopeDate($this->storeManager->getStore()->getId()), + $this->storeManager->getStore()->getWebsiteId(), + $this->customerSession->getCustomerGroupId(), + $this->product->getId() + ); $this->value = $this->value ? (float)$this->value : false; } if ($this->value) { @@ -107,17 +102,4 @@ public function getValue() return $this->value; } - - /** - * @return Rule - * @deprecated 100.1.1 - */ - private function getRuleResource() - { - if (null === $this->ruleResource) { - $this->ruleResource = ObjectManager::getInstance()->get(Rule::class); - } - - return $this->ruleResource; - } } diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCatalogPriceRuleFormActionGroup.xml new file mode 100644 index 0000000000000..ef498b95a5dca --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCatalogPriceRuleFormActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertCustomerGroupOnCatalogPriceRuleForm"> + <arguments> + <argument name="customerGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminNewCatalogRulePage.url}}" stepKey="amOnCatalogPriceRuleCreatePage"/> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.customerGroups}}" stepKey="waitForElementVisible"/> + <see selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroupName}}" stepKey="assertCustomerGroupPresentOnCatalogPriceRuleForm"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..4599e325e39cb --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateNewCatalogPriceRuleActionGroup"> + <arguments> + <argument name="catalogRule" defaultValue="_defaultCatalogRule"/> + <argument name="customerGroup" type="string"/> + </arguments> + <amOnPage url="{{AdminNewCatalogPriceRulePage.url}}" stepKey="openNewCatalogPriceRulePage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <fillField stepKey="fillName" selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}"/> + <fillField stepKey="fillDescription" selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}"/> + <selectOption selector="{{AdminNewCatalogPriceRule.status}}" userInput="{{catalogRule.is_active}}" stepKey="selectStatus"/> + <selectOption stepKey="selectWebSite" selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{catalogRule.website_ids[0]}}"/> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="scrollToActionTab"/> + <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> + <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> + <fillField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <selectOption stepKey="discardSubsequentRules" selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes"/> + <waitForPageLoad stepKey="waitForApplied"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml new file mode 100644 index 0000000000000..ea2d6821917e3 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteCatalogRuleActionGroup"> + <click selector="{{AdminNewCatalogPriceRule.delete}}" stepKey="clickOnDeleteButton"/> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.okButton}}" stepKey="waitForOkButtonToBeVisible"/> + <click selector="{{AdminNewCatalogPriceRule.okButton}}" stepKey="clickOnOkButton"/> + <waitForPageLoad stepKey="waitForPagetoLoad"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You deleted the rule." stepKey="seeSuccessDeleteMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml index 072e8b24b0336..8651a17cb969e 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminOpenNewCatalogPriceRuleFormPageActionGroup"> - <amOnPage url="{{CatalogRuleNewPage.url}}" stepKey="openNewCatalogPriceRulePage" /> + <amOnPage url="{{AdminNewCatalogRulePage.url}}" stepKey="openNewCatalogPriceRulePage" /> <waitForPageLoad stepKey="waitForPageLoad" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml new file mode 100644 index 0000000000000..2584b8b36b769 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveAndApplyRulesActionGroup"> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminNewCatalogPriceRule.save}}" stepKey="saveTheCatalogRule"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <seeElement selector="{{AdminCatalogPriceRuleGrid.updateMessage}}" stepKey="seeMessageToUpdateTheCatalogRules"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + <click stepKey="applyRules" selector="{{AdminCatalogPriceRuleGrid.applyRules}}"/> + <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="Updated rules applied."/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml new file mode 100644 index 0000000000000..c7d5d853642c0 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSearchCatalogRuleInGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + </arguments> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <waitForPageLoad stepKey="waitForPriceRulePage"/> + <click selector="{{AdminCatalogPriceRuleGrid.resetFilter}}" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForTheGridPageToLoad"/> + <fillField selector="{{AdminCatalogPriceRuleGrid.ruleFilter}}" userInput="{{catalogRuleName}}" stepKey="fillTheRuleFilter"/> + <click selector="{{AdminCatalogPriceRuleGrid.search}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForTheSearchResult"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml new file mode 100644 index 0000000000000..f0e927ea84048 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectCatalogRuleFromGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + </arguments> + <click selector="{{AdminCatalogPriceRuleGrid.selectRowByRuleName(catalogRuleName)}}" stepKey="selectRow"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml new file mode 100644 index 0000000000000..330c2ad7e15f6 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCatalogPriceRuleFormActionGroup"> + <arguments> + <argument name="catalogRule" defaultValue="inactiveCatalogRule" /> + <argument name="status" type="string" defaultValue=""/> + <argument name="websites" type="string"/> + <argument name="customerGroup" type="string"/> + </arguments> + <seeInField stepKey="fillName" selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}"/> + <seeInField stepKey="fillDescription" selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}"/> + <seeOptionIsSelected selector="{{AdminNewCatalogPriceRule.status}}" userInput="{{status}}" stepKey="selectStatus"/> + <see stepKey="seeWebSite" selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{websites}}"/> + <seeOptionIsSelected selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="scrollToActionTab"/> + <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> + <seeInField stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> + <seeInField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml new file mode 100644 index 0000000000000..8be41c3b07af6 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCatalogRuleInGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + <argument name="status" type="string" defaultValue=""/> + <argument name="websites" type="string"/> + <argument name="catalogRuleId" type="string"/> + </arguments> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{catalogRuleId}}" stepKey="seeCatalogRuleId"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{catalogRuleName}}" stepKey="seeCatalogRuleName"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{status}}" stepKey="seeCatalogRuleStatus"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{websites}}" stepKey="seeCatalogRuleWebsite"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml index b0c4f2d8a609f..de0d2baee2dd1 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml @@ -13,6 +13,7 @@ <arguments> <argument name="catalogRule" defaultValue="_defaultCatalogRule"/> </arguments> + <!-- Go to the admin Catalog rule grid and add a new one --> <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> <waitForPageLoad stepKey="waitForPriceRulePage"/> @@ -27,8 +28,8 @@ <click stepKey="clickToCalender" selector="{{AdminNewCatalogPriceRule.toDateButton}}"/> <click stepKey="clickToToday" selector="{{AdminNewCatalogPriceRule.todayDate}}"/> <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> - <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> <fillField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> <selectOption stepKey="discardSubsequentRules" selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes"/> <!-- Scroll to top and either save or save and apply after the action group --> @@ -36,7 +37,6 @@ <waitForPageLoad stepKey="waitForApplied"/> </actionGroup> - <actionGroup name="createCatalogPriceRule"> <arguments> <argument name="catalogRule" defaultValue="_defaultCatalogRule"/> @@ -53,6 +53,26 @@ <waitForPageLoad stepKey="waitForApplied"/> </actionGroup> + <actionGroup name="CreateCatalogPriceRuleViaTheUi"> + <arguments> + <argument name="catalogRule" defaultValue="_defaultCatalogRule"/> + <argument name="customerGroup" defaultValue="General" type="string"/> + <argument name="disregardRules" defaultValue="Yes" type="string"/> + </arguments> + + <fillField selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}" stepKey="fillName1"/> + <fillField selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}" stepKey="fillDescription1"/> + <selectOption selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{catalogRule.website_ids[0]}}" stepKey="selectWebSite1"/> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup1"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="scrollToActionTab1"/> + <click selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="openActionDropdown1"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}" stepKey="discountType1"/> + <fillField selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}" stepKey="fillDiscountValue1"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="{{disregardRules}}" stepKey="discardSubsequentRules1"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <scrollToTopOfPage stepKey="scrollToTop1"/> + </actionGroup> + <actionGroup name="CreateCatalogPriceRuleConditionWithAttribute"> <arguments> <argument name="attributeName" type="string"/> @@ -116,4 +136,25 @@ <actionGroup name="selectNotLoggedInCustomerGroupActionGroup"> <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="NOT LOGGED IN" stepKey="selectCustomerGroup"/> </actionGroup> + + <!-- Open rule for Edit --> + <actionGroup name="OpenCatalogPriceRule"> + <arguments> + <argument name="ruleName" type="string" defaultValue="CustomCatalogRule.name"/> + </arguments> + <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToAdminCatalogPriceRuleGridPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <fillField selector="{{AdminCatalogPriceRuleGridSection.filterByRuleName}}" userInput="{{ruleName}}" stepKey="filterByRuleName"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearch"/> + <click selector="{{AdminGridTableSection.row('1')}}" stepKey="clickEdit"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> + + <actionGroup name="RemoveCatalogPriceRule" extends="OpenCatalogPriceRule"> + <click selector="{{AdminMainActionsSection.delete}}" after="waitForPageLoad" stepKey="clickToDelete"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" after="clickToDelete" stepKey="waitForElementVisible"/> + <click selector="{{AdminConfirmationModalSection.ok}}" after="waitForElementVisible" stepKey="clickToConfirm"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" after="clickToConfirm" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You deleted the rule." after="waitForSuccessMessage" stepKey="verifyRuleIsDeleted"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml new file mode 100644 index 0000000000000..da961abac304b --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CatalogSelectCustomerGroupActionGroup"> + <arguments> + <argument name="customerGroupName" defaultValue="NOT LOGGED IN" type="string"/> + </arguments> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroupName}}" stepKey="selectCustomerGroup"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml new file mode 100644 index 0000000000000..41323b8f3b4dc --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="targetSelectValue" type="string"/> + <argument name="indexA" type="string"/> + <argument name="indexB" type="string"/> + </arguments> + <click selector="{{AdminNewCatalogPriceRule.conditionsTab}}" stepKey="openConditionsTab"/> + <waitForPageLoad stepKey="waitForConditionTabOpened"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.newCondition}}" stepKey="addNewCondition"/> + <selectOption selector="{{AdminNewCatalogPriceRuleConditions.conditionSelect(indexA)}}" userInput="{{attributeName}}" stepKey="selectTypeCondition"/> + <waitForElement selector="{{AdminNewCatalogPriceRuleConditions.ellipsisValue(indexA)}}" stepKey="waitForTarget"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.ellipsisValue(indexA)}}" stepKey="clickOnEllipsis"/> + <selectOption selector="{{AdminNewCatalogPriceRuleConditions.targetEllipsisSelect(indexA, indexB)}}" userInput="{{targetSelectValue}}" stepKey="selectOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..bc75414e0de21 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SaveAndApplyCatalogPriceRuleActionGroup"> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="waitForSaveAndApplyButton"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml index 5b75708d1ae0a..5c6ea970d3b7a 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml @@ -94,4 +94,67 @@ <data key="simple_action">by_percent</data> <data key="discount_amount">10</data> </entity> + + <entity name="InactiveCatalogRule" type="catalogRule"> + <data key="name" unique="suffix">InactiveCatalogRule</data> + <data key="description">Inactive Catalog Price Rule Description</data> + <data key="is_active">0</data> + <array key="customer_group_ids"> + <item>1</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_fixed</data> + <data key="discount_amount">10</data> + </entity> + + <entity name="CatalogRuleWithoutDiscount" type="catalogRule"> + <data key="name" unique="suffix">CatalogPriceRuleWithoutDiscount</data> + <data key="description">Catalog Price Rule Description</data> + <data key="is_active">1</data> + <array key="customer_group_ids"> + <item>0</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">0</data> + </entity> + + <entity name="ActiveCatalogPriceRuleWithConditions" type="catalogRule"> + <data key="name" unique="suffix">Active Catalog Rule with conditions </data> + <data key="description">Rule Description</data> + <data key="is_active">1</data> + <array key="customer_group_ids"> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + </entity> + + <!-- DO NOT USE IN OTHER TESTS AS IT WILL BREAK THE EXISTING TESTS --> + <entity name="DeleteActiveCatalogPriceRuleWithConditions" type="catalogRule"> + <data key="name" unique="suffix">Delete Active Catalog Rule with conditions </data> + <data key="description">Rule Description</data> + <data key="is_active">1</data> + <array key="customer_group_ids"> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + </entity> </entities> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml new file mode 100644 index 0000000000000..24e4b27604f0d --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCatalogPriceRuleGridPage" url="catalog_rule/promo_catalog/" module="Magento_CatalogRule" area="admin"> + <section name="AdminCatalogPriceRuleGridSection"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.xml similarity index 74% rename from app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml rename to app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.xml index ad3e40b37c5b0..fded4f5e5f322 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.xml @@ -8,7 +8,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> - <page name="CatalogRuleNewPage" url="catalog_rule/promo_catalog/new/" module="Magento_CatalogRule" area="admin"> + <page name="AdminNewCatalogPriceRulePage" url="catalog_rule/promo_catalog/new/" module="Magento_CatalogRule" area="admin"> <section name="AdminNewCatalogPriceRule"/> </page> </pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogRulePage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogRulePage.xml new file mode 100644 index 0000000000000..c5307bf4e22f9 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogRulePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminNewCatalogRulePage" url="catalog_rule/promo_catalog/new/" module="Magento_CatalogRule" area="admin"> + </page> +</pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml new file mode 100644 index 0000000000000..21f1401b624c9 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleGridSection"> + <element name="filterByRuleName" type="input" selector="#promo_catalog_grid_filter_name"/> + <element name="attribute" type="text" selector="//*[@id='promo_catalog_grid_table']//td[contains(text(), '{{attributeValue}}')]" parameterized="true"/> + <element name="applyRulesButton" type="button" selector="#apply_rules" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml index 635260888e7fb..5cb69cb6d7f45 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml @@ -13,6 +13,8 @@ <element name="save" type="button" selector="#save" timeout="30"/> <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> + <element name="okButton" type="button" selector="//button[@class='action-primary action-accept']"/> + <element name="successMessage" type="text" selector="#messages"/> <element name="ruleName" type="input" selector="[name='name']"/> <element name="description" type="textarea" selector="[name='description']"/> @@ -42,6 +44,8 @@ <element name="conditionSelect" type="select" selector="select#conditions__{{var}}__new_child" parameterized="true"/> <element name="targetEllipsis" type="button" selector="//li[{{var}}]//a[@class='label'][text() = '...']" parameterized="true"/> <element name="targetEllipsisValue" type="button" selector="//ul[@id='conditions__{{var}}__children']//a[contains(text(), '{{var1}}')]" parameterized="true" timeout="30"/> + <element name="ellipsisValue" type="button" selector="//ul[@id='conditions__{{var}}__children']//a[contains(text(), '...')]" parameterized="true" timeout="30"/> + <element name="targetEllipsisSelect" type="select" selector="select#conditions__{{var1}}--{{var2}}__value" parameterized="true" timeout="30"/> <element name="targetSelect" type="select" selector="//ul[@id='conditions__{{var}}__children']//select" parameterized="true" timeout="30"/> <element name="targetInput" type="input" selector="input#conditions__{{var1}}--{{var2}}__value" parameterized="true"/> <element name="applyButton" type="button" selector="#conditions__{{var1}}__children li:nth-of-type({{var2}}) a.rule-param-apply" parameterized="true"/> @@ -49,5 +53,11 @@ <section name="AdminCatalogPriceRuleGrid"> <element name="applyRules" type="button" selector="#apply_rules" timeout="30"/> + <element name="updateMessage" type="text" selector="//div[@class='message message-notice notice']//div[contains(.,'We found updated rules that are not applied. Please click')]"/> + <element name="ruleFilter" type="input" selector="//td[@data-column='name']/input[@name='name']"/> + <element name="resetFilter" type="button" selector="//button[@title='Reset Filter']" timeout="30"/> + <element name="search" type="button" selector="//div[@id='promo_catalog_grid']//button[@title='Search']" timeout="30"/> + <element name="selectRowByRuleName" type="text" selector="//tr[@data-role='row']//td[contains(.,'{{ruleName}}')]" parameterized="true"/> + <element name="firstRow" type="text" selector="//tr[@data-role='row']"/> </section> </sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index 875a7842f21ff..741da96179b8c 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -16,9 +16,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-74"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <createData entity="ApiCategory" stepKey="createCategoryOne"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml index 10db68e9053d7..befe0b0ce7f98 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-65"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <!-- Create the simple product and category that it will be in --> @@ -77,9 +74,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-93"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -103,9 +97,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-69"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -129,9 +120,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-60"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -155,9 +143,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-71"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <!-- Create a simple product and a category--> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml new file mode 100644 index 0000000000000..5223b18df4e4a --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateInactiveCatalogPriceRuleTest"> + <annotations> + <stories value="Create Catalog Price Rule"/> + <title value="Create Inactive Catalog Price Rule"/> + <description value="Login as admin and create inactive catalog price Rule"/> + <testCaseId value="MC-13977"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <actionGroup ref="AdminSelectCatalogRuleFromGridActionGroup" stepKey="selectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <actionGroup ref="AdminDeleteCatalogRuleActionGroup" stepKey="deleteTheCatalogRule"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create Inactive Catalog Price Rule --> + <actionGroup ref="AdminCreateNewCatalogPriceRuleActionGroup" stepKey="createCatalogPriceRule"> + <argument name="catalogRule" value="InactiveCatalogRule"/> + <argument name="customerGroup" value="General"/> + </actionGroup> + + <!-- Save and Apply Rules --> + <actionGroup ref="AdminSaveAndApplyRulesActionGroup" stepKey="saveAndApplyRules"/> + + <!-- Search Catalog Rule in Grid --> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchAndSelectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + + <!--Select Catalog Rule in Grid --> + <actionGroup ref="AdminSelectCatalogRuleFromGridActionGroup" stepKey="selectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <grabFromCurrentUrl stepKey="catalogRuleId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Assert catalog Price Rule Form --> + <actionGroup ref="AssertCatalogPriceRuleFormActionGroup" stepKey="assertCatalogRuleForm"> + <argument name="catalogRule" value="InactiveCatalogRule"/> + <argument name="status" value="Inactive"/> + <argument name="websites" value="Main Website"/> + <argument name="customerGroup" value="General"/> + </actionGroup> + + <!-- Search Catalog Rule in Grid --> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + + <!-- Assert Catalog Rule In Grid --> + <actionGroup ref="AssertCatalogRuleInGridActionGroup" stepKey="assertCatalogRuleInGrid"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + <argument name="status" value="Inactive"/> + <argument name="websites" value="Main Website"/> + <argument name="catalogRuleId" value="$catalogRuleId"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml new file mode 100644 index 0000000000000..06392764290ac --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest"> + <annotations> + <stories value="Delete Catalog Price Rule"/> + <title value="Delete Catalog Price Rule for Simple Product"/> + <description value="Assert that Catalog Price Rule is not applied for simple product."/> + <testCaseId value="MC-14073"/> + <severity value="CRITICAL"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer1"/> + <createData entity="_defaultCategory" stepKey="createCategory1"/> + <createData entity="SimpleProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <amOnPage url="{{AdminNewCatalogPriceRulePage.url}}" stepKey="openNewCatalogPriceRulePage"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + + <actionGroup ref="CreateCatalogPriceRuleViaTheUi" stepKey="createCatalogPriceRuleViaTheUi1"> + <argument name="catalogRule" value="DeleteActiveCatalogPriceRuleWithConditions"/> + <argument name="customerGroup" value="General"/> + <argument name="disregardRules" value="Yes"/> + </actionGroup> + + <click selector="{{AdminNewCatalogPriceRule.save}}" stepKey="saveTheCatalogRule"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> + + <deleteData createDataKey="createCustomer1" stepKey="deleteCustomer1"/> + <deleteData createDataKey="createProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createCategory1" stepKey="deleteCategoryFirst1"/> + </after> + + <!-- Delete the simple product and catalog price rule --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage1"/> + <waitForPageLoad stepKey="waitForPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRule1"> + <argument name="name" value="{{DeleteActiveCatalogPriceRuleWithConditions.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + + <!-- Assert that the Success message is present after the delete --> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You deleted the rule." stepKey="seeDeletedRuleMessage1"/> + + <!-- Reindex --> + <magentoCLI command="cache:flush" stepKey="flushCache1"/> + <magentoCLI command="indexer:reindex" stepKey="reindex1"/> + + <!-- Assert that the rule isn't present on the Category page --> + <amOnPage url="$$createCategory1.name$$.html" stepKey="goToStorefrontCategoryPage1"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <dontSee selector="{{StorefrontCategoryProductSection.ProductCatalogRulePriceTitleByName($$createProduct1.name$$)}}" userInput="Regular Price" stepKey="dontSeeRegularPriceText1"/> + <dontSeeElement selector="{{StorefrontCategoryProductSection.ProductCatalogRuleSpecialPriceTitleByName($$createProduct1.name$$)}}" stepKey="dontSeeSpecialPrice1"/> + + <!-- Assert that the rule isn't present on the Product page --> + <amOnPage url="$$createProduct1.name$$.html" stepKey="goToStorefrontProductPage1"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <dontSee selector="{{StorefrontProductInfoMainSection.oldPriceTag}}" userInput="Regular Price" stepKey="dontSeeRegularPRiceText2"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$$createProduct1.price$$" stepKey="seeTrueProductPrice1"/> + + <!-- Assert that the rule isn't present in the Shopping Cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProductToShoppingCart1"> + <argument name="productName" value="$$createProduct1.name$$"/> + </actionGroup> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="openMiniShoppingCart1"/> + <see selector="{{StorefrontMinicartSection.productPriceByName($$createProduct1.name$$)}}" userInput="$$createProduct1.price$$" stepKey="seeCorrectProductPrice1"/> + + <!-- Assert that the rule isn't present on the Checkout page --> + <click selector="{{StorefrontMiniCartSection.goToCheckout}}" stepKey="goToCheckout1"/> + <conditionalClick selector="{{CheckoutCartSummarySection.expandShoppingCartSummary}}" dependentSelector="{{CheckoutCartSummarySection.expandShoppingCartSummary}}" visible="true" stepKey="expandShoppingCartSummary1"/> + <see selector="{{CheckoutCartProductSection.ProductRegularPriceByName($$createProduct1.name$$)}}" userInput="$$createProduct1.price$$" stepKey="seeCorrectProductPriceOnCheckout1"/> + </test> + + <test name="AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest"> + <annotations> + <stories value="Delete Catalog Price Rule"/> + <title value="Delete Catalog Price Rule for Configurable Product"/> + <description value="Assert that Catalog Price Rule is not applied for configurable product"/> + <testCaseId value="MC-14074"/> + <severity value="CRITICAL"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer1"/> + <createData entity="SimpleSubCategory" stepKey="createCategory1"/> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct1"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <!-- Make the configurable product have two options, that are children of the default attribute set --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute1"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption1"> + <requiredEntity createDataKey="createConfigProduct1"/> + <requiredEntity createDataKey="createConfigProductAttribute1"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct1"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct1"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <amOnPage url="{{AdminNewCatalogPriceRulePage.url}}" stepKey="openNewCatalogPriceRulePage"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + + <actionGroup ref="CreateCatalogPriceRuleViaTheUi" stepKey="createCatalogPriceRuleViaTheUi1"> + <argument name="catalogRule" value="DeleteActiveCatalogPriceRuleWithConditions"/> + <argument name="customerGroup" value="General"/> + <argument name="disregardRules" value="Yes"/> + </actionGroup> + + <click selector="{{AdminNewCatalogPriceRule.save}}" stepKey="saveTheCatalogRule"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> + + <deleteData createDataKey="createCustomer1" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory1" stepKey="deleteCategory1"/> + <deleteData createDataKey="createConfigProduct1" stepKey="deleteConfigProduct1"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute1" stepKey="deleteConfigProductAttribute1"/> + </after> + + <!-- Delete the simple product and catalog price rule --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage1"/> + <waitForPageLoad stepKey="waitForPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRule1"> + <argument name="name" value="{{DeleteActiveCatalogPriceRuleWithConditions.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You deleted the rule." stepKey="seeDeletedRuleMessage1"/> + + <!-- Reindex --> + <magentoCLI command="cache:flush" stepKey="flushCache1"/> + <magentoCLI command="indexer:reindex" stepKey="reindex1"/> + + <!-- Assert that the rule isn't present on the Category page --> + <amOnPage url="$$createCategory1.name$$.html" stepKey="goToStorefrontCategoryPage1"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName($$createConfigProduct1.name$$)}}" userInput="$$createConfigChildProduct1.price$$" stepKey="seeRegularPriceText1"/> + + <!-- Assert that the rule isn't present on the Product page --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct1.custom_attributes[url_key]$$)}}" stepKey="goToStorefrontProductPage1"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <dontSee selector="{{StorefrontProductInfoMainSection.oldPriceTag}}" userInput="Regular Price" stepKey="dontSeeRegularPriceText2"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$$createConfigChildProduct1.price$$" stepKey="seeTrueProductPrice1"/> + + <!-- Assert that the rule isn't present in the Shopping Cart --> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="option1" stepKey="selectOption1"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart1"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad4"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="You added $$createConfigProduct1.name$ to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="openMiniShoppingCart1"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad5"/> + <see selector="{{StorefrontMinicartSection.productPriceByName($$createConfigProduct1.name$$)}}" userInput="$$createConfigProduct1.price$$" stepKey="seeCorrectProductPrice1"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml index 06f3682aedd85..d3546d06492be 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-160"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml index 053a8c33e640c..f490da2947232 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml @@ -11,6 +11,7 @@ <test name="AdminEnableAttributeIsUndefinedCatalogPriceRuleTest"> <annotations> <features value="CatalogRule"/> + <stories value="Catalog price rule"/> <title value="Enable 'is undefined' condition to Scope Catalog Price rules by custom product attribute"/> <description value="Enable 'is undefined' condition to Scope Catalog Price rules by custom product attribute"/> <severity value="CRITICAL"/> @@ -62,6 +63,8 @@ <deleteData createDataKey="createSecondProductAttribute" stepKey="deleteSecondProductAttribute"/> <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </after> <!--Create catalog price rule--> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml new file mode 100644 index 0000000000000..b9e7bfb4d41e4 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ApplyCatalogPriceRuleForSimpleProductAndConfigurableProductTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product and configurable product"/> + <description value="Admin should be able to apply the catalog price rule for simple product and configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14770"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create Simple Product --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Make the configurable product have two options, that are children of the default attribute set --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + <after> + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Delete products and category --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> + </after> + <!-- Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check simple product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check simple product price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check simple product regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check configurable product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Name"> + <argument name="productInfo" value="$createConfigProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check configurable product price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Price"> + <argument name="productInfo" value="$110.70"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to simple product on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices"> + <argument name="productPrice" value="$56.78"/> + <argument name="productFinalPrice" value="$51.10"/> + </actionGroup> + + <!-- Add simple product to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Open configurable product 1 select option 1 attribute --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.custom_attributes[url_key]$$)}}" stepKey="amOnConfigurableProductPage"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect('$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$')}}" userInput="$$createConfigProductAttributeOption1.option[store_labels][0][label]$$" stepKey="selectOption"/> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices2"> + <argument name="productPrice" value="$123.00"/> + <argument name="productFinalPrice" value="$110.70"/> + </actionGroup> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddConfigProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$161.80"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml new file mode 100644 index 0000000000000..3405d0c4e776d --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ApplyCatalogRuleForSimpleProductAndFixedMethodTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <description value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14771"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product --> + <createData entity="_defaultProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Update all products to have custom options --> + <updateData createDataKey="createProduct1" entity="productWithFixedOptions" stepKey="updateProductWithOptions1"/> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{CatalogRuleByFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + <argument name ="catalogRule" value="CatalogRuleByFixed"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createProduct1.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices1"> + <argument name="customOption" value="ProductOptionRadioButton2"/> + <argument name="customOptionValue" value="ProductOptionValueRadioButtons1"/> + <argument name="productPrice" value="$156.77"/> + <argument name="productFinalPrice" value="$144.47"/> + </actionGroup> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$144.47"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml new file mode 100644 index 0000000000000..c3bcde92bd1f2 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product for new customer group"/> + <description value="Admin should be able to apply the catalog price rule for simple product for new customer group"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14772"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create new customer group --> + <createData entity="CustomCustomerGroup" stepKey="customerGroup" /> + + <!--Creating customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer" > + <field key="group_id">$customerGroup.id$</field> + </createData> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <!-- Delete customer group --> + <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{CatalogRuleByFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + <argument name="catalogRule" value="CatalogRuleByFixed"/> + </actionGroup> + + <!-- Select custom customer group --> + <actionGroup ref="CatalogSelectCustomerGroupActionGroup" stepKey="selectCustomCustomerGroup"> + <argument name="customerGroupName" value="$customerGroup.code$"/> + </actionGroup> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductName"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 has no discounts applied on store front category page --> + <actionGroup ref="AssertDontSeeProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductRegularPrice"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage"/> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$56.78"/> + </actionGroup> + + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Navigate to category on store front as customer--> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPageAsCustomer"/> + + <!-- Check simple product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductNameAsCustomer"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check simple product price on store front category page as customer --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductPriceAsCustomer"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check simple product regular price on store front category page as customer --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductRegularPriceAsCustomer"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to simple product on store front as customer --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage1AsCustomer"/> + + <!-- Assert regular and special price as customer--> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices"> + <argument name="productPrice" value="$56.78"/> + <argument name="productFinalPrice" value="$44.48"/> + </actionGroup> + + <!-- Add simple product to cart as customer --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProductToCartAsCustomer"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart as customer --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCartAsCustomer"> + <argument name="subTotal" value="$88.96"/> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml new file mode 100644 index 0000000000000..055eacaeb2b78 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ApplyCatalogRuleForSimpleProductWithCustomOptionsTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <description value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14769"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product 1 --> + <createData entity="_defaultProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create Simple Product 2 --> + <createData entity="_defaultProduct" stepKey="createProduct2"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create Simple Product 3 --> + <createData entity="_defaultProduct" stepKey="createProduct3"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Update all products to have custom options --> + <updateData createDataKey="createProduct1" entity="productWithCustomOptions" stepKey="updateProductWithOptions1"/> + <updateData createDataKey="createProduct2" entity="productWithCustomOptions" stepKey="updateProductWithOptions2"/> + <updateData createDataKey="createProduct3" entity="productWithCustomOptions" stepKey="updateProductWithOptions3"/> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createProduct1.name$"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 1 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 2 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Name"> + <argument name="productInfo" value="$createProduct2.name$"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 2 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 2 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 3 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3Name"> + <argument name="productInfo" value="$createProduct3.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 3 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 3 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices1"> + <argument name="customOption" value="{{ProductOptionValueDropdown1.title}} +$0.01"/> + <argument name="productPrice" value="$56.79"/> + <argument name="productFinalPrice" value="$51.11"/> + </actionGroup> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Navigate to product 2 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage2"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown3 --> + <actionGroup ref="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices2"> + <argument name="customOption" value="{{ProductOptionValueDropdown3.title}} +$5.11"/> + <argument name="productPrice" value="$62.46"/> + <argument name="productFinalPrice" value="$56.21"/> + </actionGroup> + + <!-- Add product 2 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct2ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct3.name$)}}" stepKey="goToProductPage3"/> + + <!-- Add product 3 to cart with no custom option --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct3ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$158.42"/> + </actionGroup> + + <!-- Navigate to checkout shipping page --> + <amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/> + <waitForPageLoad stepKey="waitFoCheckoutShippingPageLoad"/> + + <!-- Fill Shipping information --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="fillOrderShippingInfo"> + <argument name="customerVar" value="Simple_US_Customer"/> + <argument name="customerAddressVar" value="US_Address_TX"/> + </actionGroup> + + <!-- Verify order summary on payment page --> + <actionGroup ref="VerifyCheckoutPaymentOrderSummaryActionGroup" stepKey="verifyCheckoutPaymentOrderSummaryActionGroup"> + <argument name="orderSummarySubTotal" value="$158.42"/> + <argument name="orderSummaryShippingTotal" value="$15.00"/> + <argument name="orderSummaryTotal" value="$173.42"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml index e3eac52a8d40b..738f193fcc511 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml @@ -17,9 +17,6 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-69455"/> <group value="persistent"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index c6ecc1c6d9658..b486654fe9acf 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -17,9 +17,6 @@ <severity value="CRITICAL"/> <testCaseId value="MC-79"/> <group value="CatalogRule"/> - <skip> - <issueId value="MC-5777"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="login"/> @@ -57,6 +54,7 @@ <!-- Verify price is not discounted in the cart --> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> <waitForPageLoad stepKey="waitForCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> <waitForPageLoad stepKey="waitForCheckout"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$$createProduct.price$$" stepKey="seePrice3"/> diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php index 521e4e1d59897..920dcb8e1ede5 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php @@ -144,14 +144,12 @@ protected function setUp() ); $this->ruleCollectionFactory = $this->createPartialMock( \Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory::class, - ['create', 'addFieldToFilter'] + ['create'] ); $this->backend = $this->createMock(\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend::class); $this->select = $this->createMock(\Magento\Framework\DB\Select::class); $this->metadataPool = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); - $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadata::class) - ->disableOriginalConstructor() - ->getMock(); + $metadata = $this->createMock(\Magento\Framework\EntityManager\EntityMetadata::class); $this->metadataPool->expects($this->any())->method('getMetadata')->willReturn($metadata); $this->connection = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); $this->db = $this->createMock(\Zend_Db_Statement_Interface::class); @@ -181,10 +179,16 @@ protected function setUp() $this->rules->expects($this->any())->method('getWebsiteIds')->will($this->returnValue([1])); $this->rules->expects($this->any())->method('getCustomerGroupIds')->will($this->returnValue([1])); - $this->ruleCollectionFactory->expects($this->any())->method('create')->will($this->returnSelf()); - $this->ruleCollectionFactory->expects($this->any())->method('addFieldToFilter')->will( - $this->returnValue([$this->rules]) - ); + $ruleCollection = $this->createMock(\Magento\CatalogRule\Model\ResourceModel\Rule\Collection::class); + $this->ruleCollectionFactory->expects($this->once()) + ->method('create') + ->willReturn($ruleCollection); + $ruleCollection->expects($this->once()) + ->method('addFieldToFilter') + ->willReturnSelf(); + $ruleIterator = new \ArrayIterator([$this->rules]); + $ruleCollection->method('getIterator') + ->willReturn($ruleIterator); $this->product->expects($this->any())->method('load')->will($this->returnSelf()); $this->product->expects($this->any())->method('getId')->will($this->returnValue(1)); @@ -213,19 +217,20 @@ protected function setUp() ] ); - $this->reindexRuleProductPrice = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice::class) - ->disableOriginalConstructor() - ->getMock(); - $this->reindexRuleGroupWebsite = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\ReindexRuleGroupWebsite::class) - ->disableOriginalConstructor() - ->getMock(); - $this->setProperties($this->indexBuilder, [ - 'metadataPool' => $this->metadataPool, - 'reindexRuleProductPrice' => $this->reindexRuleProductPrice, - 'reindexRuleGroupWebsite' => $this->reindexRuleGroupWebsite - ]); + $this->reindexRuleProductPrice = $this->createMock( + \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice::class + ); + $this->reindexRuleGroupWebsite = $this->createMock( + \Magento\CatalogRule\Model\Indexer\ReindexRuleGroupWebsite::class + ); + $this->setProperties( + $this->indexBuilder, + [ + 'metadataPool' => $this->metadataPool, + 'reindexRuleProductPrice' => $this->reindexRuleProductPrice, + 'reindexRuleGroupWebsite' => $this->reindexRuleGroupWebsite, + ] + ); } /** diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php index 6d7f0673ed281..5f63283df6760 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php @@ -6,65 +6,62 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; -use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Catalog\Model\Product; +use Magento\CatalogRule\Model\Indexer\ProductPriceCalculator; +use Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice; +use Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor; +use Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Api\Data\GroupInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; class ReindexRuleProductPriceTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice + * @var ReindexRuleProductPrice */ private $model; /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ private $storeManagerMock; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var RuleProductsSelectBuilder|MockObject */ private $ruleProductsSelectBuilderMock; /** - * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator|\PHPUnit_Framework_MockObject_MockObject + * @var ProductPriceCalculator|MockObject */ private $productPriceCalculatorMock; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime|\PHPUnit_Framework_MockObject_MockObject + * @var TimezoneInterface|MockObject */ - private $dateTimeMock; + private $localeDate; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor|\PHPUnit_Framework_MockObject_MockObject + * @var RuleProductPricesPersistor|MockObject */ private $pricesPersistorMock; protected function setUp() { - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->ruleProductsSelectBuilderMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productPriceCalculatorMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\ProductPriceCalculator::class) - ->disableOriginalConstructor() - ->getMock(); - $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) - ->disableOriginalConstructor() - ->getMock(); - $this->pricesPersistorMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor::class) - ->disableOriginalConstructor() - ->getMock(); - $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice( + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->ruleProductsSelectBuilderMock = $this->createMock(RuleProductsSelectBuilder::class); + $this->productPriceCalculatorMock = $this->createMock(ProductPriceCalculator::class); + $this->localeDate = $this->createMock(TimezoneInterface::class); + $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class); + + $this->model = new ReindexRuleProductPrice( $this->storeManagerMock, $this->ruleProductsSelectBuilderMock, $this->productPriceCalculatorMock, - $this->dateTimeMock, + $this->localeDate, $this->pricesPersistorMock ); } @@ -72,19 +69,32 @@ protected function setUp() public function testExecute() { $websiteId = 234; - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $websiteMock = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $websiteMock->expects($this->once())->method('getId')->willReturn($websiteId); - $this->storeManagerMock->expects($this->once())->method('getWebsites')->willReturn([$websiteMock]); - - $statementMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class) - ->disableOriginalConstructor() - ->getMock(); + $defaultGroupId = 11; + $defaultStoreId = 22; + + $websiteMock = $this->createMock(WebsiteInterface::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn($websiteId); + $websiteMock->expects($this->once()) + ->method('getDefaultGroupId') + ->willReturn($defaultGroupId); + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([$websiteMock]); + $groupMock = $this->createMock(GroupInterface::class); + $groupMock->method('getId') + ->willReturn($defaultStoreId); + $groupMock->expects($this->once()) + ->method('getDefaultStoreId') + ->willReturn($defaultStoreId); + $this->storeManagerMock->expects($this->once()) + ->method('getGroup') + ->with($defaultGroupId) + ->willReturn($groupMock); + + $productMock = $this->createMock(Product::class); + $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class); $this->ruleProductsSelectBuilderMock->expects($this->once()) ->method('build') ->with($websiteId, $productMock, true) @@ -99,29 +109,22 @@ public function testExecute() 'action_stop' => true ]; - $this->dateTimeMock->expects($this->at(0)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['from_time']) - ->willReturn($ruleData['from_time']); - $this->dateTimeMock->expects($this->at(1)) - ->method('timestamp') - ->with($ruleData['from_time']) - ->willReturn($ruleData['from_time']); - - $this->dateTimeMock->expects($this->at(2)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['to_time']) - ->willReturn($ruleData['to_time']); - $this->dateTimeMock->expects($this->at(3)) - ->method('timestamp') - ->with($ruleData['to_time']) - ->willReturn($ruleData['to_time']); - - $statementMock->expects($this->at(0))->method('fetch')->willReturn($ruleData); - $statementMock->expects($this->at(1))->method('fetch')->willReturn(false); - - $this->productPriceCalculatorMock->expects($this->atLeastOnce())->method('calculate'); - $this->pricesPersistorMock->expects($this->once())->method('execute'); + $this->localeDate->expects($this->once()) + ->method('scopeDate') + ->with($defaultStoreId, null, true) + ->willReturn(new \DateTime()); + + $statementMock->expects($this->at(0)) + ->method('fetch') + ->willReturn($ruleData); + $statementMock->expects($this->at(1)) + ->method('fetch') + ->willReturn(false); + + $this->productPriceCalculatorMock->expects($this->atLeastOnce()) + ->method('calculate'); + $this->pricesPersistorMock->expects($this->once()) + ->method('execute'); $this->assertTrue($this->model->execute(1, $productMock, true)); } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index 0dbbaee8d2871..a86ab736fb289 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -8,89 +8,96 @@ use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; +use Magento\CatalogRule\Model\Indexer\ReindexRuleProduct; +use Magento\CatalogRule\Model\Rule; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\MockObject\MockObject; class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct + * @var ReindexRuleProduct */ private $model; /** - * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + * @var ResourceConnection|MockObject */ private $resourceMock; /** - * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|MockObject */ private $activeTableSwitcherMock; /** - * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + * @var IndexerTableSwapperInterface|MockObject */ private $tableSwapperMock; + /** + * @var TimezoneInterface|MockObject + */ + private $localeDateMock; + protected function setUp() { - $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); - $this->tableSwapperMock = $this->getMockForAbstractClass( - IndexerTableSwapperInterface::class - ); - $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct( + $this->resourceMock = $this->createMock(ResourceConnection::class); + $this->activeTableSwitcherMock = $this->createMock(ActiveTableSwitcher::class); + $this->tableSwapperMock = $this->createMock(IndexerTableSwapperInterface::class); + $this->localeDateMock = $this->createMock(TimezoneInterface::class); + + $this->model = new ReindexRuleProduct( $this->resourceMock, $this->activeTableSwitcherMock, - $this->tableSwapperMock + $this->tableSwapperMock, + $this->localeDateMock ); } public function testExecuteIfRuleInactive() { - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(false); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once()) + ->method('getIsActive') + ->willReturn(false); $this->assertFalse($this->model->execute($ruleMock, 100, true)); } public function testExecuteIfRuleWithoutWebsiteIds() { - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); - $ruleMock->expects($this->once())->method('getWebsiteIds')->willReturn(null); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once()) + ->method('getIsActive') + ->willReturn(true); + $ruleMock->expects($this->once()) + ->method('getWebsiteIds') + ->willReturn(null); $this->assertFalse($this->model->execute($ruleMock, 100, true)); } public function testExecute() { + $websiteId = 3; + $websiteTz = 'America/Los_Angeles'; $productIds = [ - 4 => [1 => 1], - 5 => [1 => 1], - 6 => [1 => 1], + 4 => [$websiteId => 1], + 5 => [$websiteId => 1], + 6 => [$websiteId => 1], ]; - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); - $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn(1); - $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); $this->tableSwapperMock->expects($this->once()) ->method('getWorkingTableName') ->with('catalogrule_product') ->willReturn('catalogrule_product_replica'); - $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); + $connectionMock = $this->createMock(AdapterInterface::class); + $this->resourceMock->expects($this->at(0)) + ->method('getConnection') + ->willReturn($connectionMock); $this->resourceMock->expects($this->at(1)) ->method('getTableName') ->with('catalogrule_product') @@ -100,21 +107,30 @@ public function testExecute() ->with('catalogrule_product_replica') ->willReturn('catalogrule_product_replica'); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); + $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn([$websiteId]); + $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); $ruleMock->expects($this->once())->method('getId')->willReturn(100); $ruleMock->expects($this->once())->method('getCustomerGroupIds')->willReturn([10]); - $ruleMock->expects($this->once())->method('getFromDate')->willReturn('2017-06-21'); - $ruleMock->expects($this->once())->method('getToDate')->willReturn('2017-06-30'); + $ruleMock->expects($this->atLeastOnce())->method('getFromDate')->willReturn('2017-06-21'); + $ruleMock->expects($this->atLeastOnce())->method('getToDate')->willReturn('2017-06-30'); $ruleMock->expects($this->once())->method('getSortOrder')->willReturn(1); $ruleMock->expects($this->once())->method('getSimpleAction')->willReturn('simple_action'); $ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43); $ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true); + $this->localeDateMock->expects($this->once()) + ->method('getConfigTimezone') + ->with(ScopeInterface::SCOPE_WEBSITE, $websiteId) + ->willReturn($websiteTz); + $batchRows = [ [ 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 4, 'action_operator' => 'simple_action', @@ -126,7 +142,7 @@ public function testExecute() 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 5, 'action_operator' => 'simple_action', @@ -141,7 +157,7 @@ public function testExecute() 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 6, 'action_operator' => 'simple_action', diff --git a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php index 797097f8a5346..7514d2bc4b5c5 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php @@ -3,11 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogRule\Test\Unit\Pricing\Price; +use Magento\Catalog\Model\Product; +use Magento\CatalogRule\Model\ResourceModel\Rule; use Magento\CatalogRule\Pricing\Price\CatalogRulePrice; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Customer\Model\Session; +use Magento\Framework\Pricing\Adjustment\Calculator; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; /** * Class CatalogRulePriceTest @@ -17,120 +27,73 @@ class CatalogRulePriceTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\CatalogRule\Pricing\Price\CatalogRulePrice + * @var CatalogRulePrice */ - protected $object; + private $object; /** - * @var \Magento\Framework\Pricing\SaleableInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ - protected $saleableItemMock; + private $saleableItemMock; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject + * @var TimezoneInterface|MockObject */ - protected $dataTimeMock; + private $dataTimeMock; /** - * @var \Magento\Store\Model\StoreManager|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ - protected $storeManagerMock; + private $storeManagerMock; /** - * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject + * @var Session|MockObject */ - protected $customerSessionMock; + private $customerSessionMock; /** - * @var \Magento\Framework\Pricing\PriceInfo\Base | \PHPUnit_Framework_MockObject_MockObject + * @var Rule|MockObject */ - protected $priceInfoMock; + private $catalogRuleResourceMock; /** - * @var \Magento\CatalogRule\Model\ResourceModel\RuleFactory|\PHPUnit_Framework_MockObject_MockObject + * @var WebsiteInterface|MockObject */ - protected $catalogRuleResourceFactoryMock; + private $coreWebsiteMock; /** - * @var \Magento\CatalogRule\Model\ResourceModel\Rule|\PHPUnit_Framework_MockObject_MockObject + * @var StoreInterface|MockObject */ - protected $catalogRuleResourceMock; + private $coreStoreMock; /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + * @var Calculator|MockObject */ - protected $coreWebsiteMock; + private $calculator; /** - * @var \Magento\Store\Model\Website|\PHPUnit_Framework_MockObject_MockObject + * @var PriceCurrencyInterface|MockObject */ - protected $coreStoreMock; - - /** - * @var \Magento\Framework\Pricing\Adjustment\Calculator|\PHPUnit_Framework_MockObject_MockObject - */ - protected $calculator; - - /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $priceCurrencyMock; + private $priceCurrencyMock; /** * Set up */ protected function setUp() { - $this->saleableItemMock = $this->createPartialMock( - \Magento\Catalog\Model\Product::class, - ['getId', '__wakeup', 'getPriceInfo', 'hasData', 'getData'] - ); - $this->dataTimeMock = $this->getMockForAbstractClass( - \Magento\Framework\Stdlib\DateTime\TimezoneInterface::class, - [], - '', - false, - true, - true, - [] - ); - - $this->coreStoreMock = $this->createMock(\Magento\Store\Model\Store::class); - $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManager::class); + $this->saleableItemMock = $this->createMock(Product::class); + $this->dataTimeMock = $this->createMock(TimezoneInterface::class); + $this->coreStoreMock = $this->createMock(StoreInterface::class); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->storeManagerMock->expects($this->any()) ->method('getStore') - ->will($this->returnValue($this->coreStoreMock)); - - $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class); - $this->priceInfoMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo::class) - ->setMethods(['getAdjustments']) - ->disableOriginalConstructor() - ->getMock(); - $this->catalogRuleResourceFactoryMock = $this->createPartialMock( - \Magento\CatalogRule\Model\ResourceModel\RuleFactory::class, - ['create'] - ); - $this->catalogRuleResourceMock = $this->createMock(\Magento\CatalogRule\Model\ResourceModel\Rule::class); - - $this->coreWebsiteMock = $this->createMock(\Magento\Store\Model\Website::class); - - $this->priceInfoMock->expects($this->any()) - ->method('getAdjustments') - ->will($this->returnValue([])); - $this->saleableItemMock->expects($this->any()) - ->method('getPriceInfo') - ->will($this->returnValue($this->priceInfoMock)); - - $this->catalogRuleResourceFactoryMock->expects($this->any()) - ->method('create') - ->will($this->returnValue($this->catalogRuleResourceMock)); - - $this->calculator = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\Calculator::class) - ->disableOriginalConstructor() - ->getMock(); + ->willReturn($this->coreStoreMock); + $this->customerSessionMock = $this->createMock(Session::class); + $this->catalogRuleResourceMock = $this->createMock(Rule::class); + $this->coreWebsiteMock = $this->createMock(WebsiteInterface::class); + $this->calculator = $this->createMock(Calculator::class); $qty = 1; - - $this->priceCurrencyMock = $this->createMock(\Magento\Framework\Pricing\PriceCurrencyInterface::class); + $this->priceCurrencyMock = $this->createMock(PriceCurrencyInterface::class); $this->object = new CatalogRulePrice( $this->saleableItemMock, @@ -140,12 +103,6 @@ protected function setUp() $this->dataTimeMock, $this->storeManagerMock, $this->customerSessionMock, - $this->catalogRuleResourceFactoryMock - ); - - (new ObjectManager($this))->setBackwardCompatibleProperty( - $this->object, - 'ruleResource', $this->catalogRuleResourceMock ); } @@ -155,38 +112,39 @@ protected function setUp() */ public function testGetValue() { - $coreStoreId = 1; - $coreWebsiteId = 1; - $productId = 1; - $customerGroupId = 1; - $dateTime = time(); + $storeId = 5; + $coreWebsiteId = 2; + $productId = 4; + $customerGroupId = 3; + $date = new \DateTime(); $catalogRulePrice = 55.12; $convertedPrice = 45.34; $this->coreStoreMock->expects($this->once()) ->method('getId') - ->will($this->returnValue($coreStoreId)); - $this->coreStoreMock->expects($this->once()) - ->method('getWebsiteId') - ->will($this->returnValue($coreWebsiteId)); + ->willReturn($storeId); $this->dataTimeMock->expects($this->once()) ->method('scopeDate') - ->with($this->equalTo($coreStoreId)) - ->will($this->returnValue($dateTime)); + ->with($storeId) + ->willReturn($date); + $this->coreStoreMock->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($coreWebsiteId); $this->customerSessionMock->expects($this->once()) ->method('getCustomerGroupId') - ->will($this->returnValue($customerGroupId)); + ->willReturn($customerGroupId); $this->catalogRuleResourceMock->expects($this->once()) ->method('getRulePrice') - ->will($this->returnValue($catalogRulePrice)); - $this->saleableItemMock->expects($this->any()) + ->with($date, $coreWebsiteId, $customerGroupId, $productId) + ->willReturn($catalogRulePrice); + $this->saleableItemMock->expects($this->once()) ->method('getId') - ->will($this->returnValue($productId)); - $this->priceCurrencyMock->expects($this->any()) + ->willReturn($productId); + $this->priceCurrencyMock->expects($this->once()) ->method('convertAndRound') ->with($catalogRulePrice) - ->will($this->returnValue($convertedPrice)); + ->willReturn($convertedPrice); $this->assertEquals($convertedPrice, $this->object->getValue()); } diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index b0823e4ac2d8f..6cc05c6e0225d 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -32,5 +32,5 @@ "Magento\\CatalogRule\\": "" } }, - "version": "101.1.2" + "version": "101.1.3" } diff --git a/app/code/Magento/CatalogRule/etc/db_schema.xml b/app/code/Magento/CatalogRule/etc/db_schema.xml index 894f057ba73d1..59082e93b04c2 100644 --- a/app/code/Magento/CatalogRule/etc/db_schema.xml +++ b/app/code/Magento/CatalogRule/etc/db_schema.xml @@ -23,7 +23,7 @@ <column xsi:type="int" name="sort_order" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Sort Order"/> <column xsi:type="varchar" name="simple_action" nullable="true" length="32" comment="Simple Action"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="false" + <column xsi:type="decimal" name="discount_amount" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="rule_id"/> @@ -49,7 +49,7 @@ default="0" comment="Product Id"/> <column xsi:type="varchar" name="action_operator" nullable="true" length="10" default="to_fixed" comment="Action Operator"/> - <column xsi:type="decimal" name="action_amount" scale="4" precision="20" unsigned="false" nullable="false" + <column xsi:type="decimal" name="action_amount" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Action Amount"/> <column xsi:type="smallint" name="action_stop" padding="6" unsigned="false" nullable="false" identity="false" default="0" comment="Action Stop"/> @@ -92,7 +92,7 @@ <column xsi:type="int" name="customer_group_id" padding="11" unsigned="false" nullable="true" identity="false"/> <column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Product Id"/> - <column xsi:type="decimal" name="rule_price" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="rule_price" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Rule Price"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website Id"/> @@ -189,7 +189,7 @@ default="0" comment="Product Id"/> <column xsi:type="varchar" name="action_operator" nullable="true" default="to_fixed" length="10" comment="Action Operator"/> - <column xsi:type="decimal" name="action_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="action_amount" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Action Amount"/> <column xsi:type="smallint" name="action_stop" padding="6" unsigned="false" nullable="false" identity="false" default="0" comment="Action Stop"/> @@ -233,7 +233,7 @@ <column xsi:type="int" name="customer_group_id" padding="11" unsigned="false" nullable="true" identity="false"/> <column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Product Id"/> - <column xsi:type="decimal" name="rule_price" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="rule_price" scale="6" precision="20" unsigned="false" nullable="false" default="0" comment="Rule Price"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website Id"/> diff --git a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/fieldset.phtml b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/fieldset.phtml index 1798b1112426c..1c3eedf43b264 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/fieldset.phtml +++ b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/fieldset.phtml @@ -4,17 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /**@var \Magento\Backend\Block\Widget\Form\Renderer\Fieldset $block */ ?> <?php $_element = $block->getElement() ?> <?php $_jsObjectName = $block->getFieldSetId() != null ? $block->getFieldSetId() : $_element->getHtmlId() ?> <div class="rule-tree"> - <fieldset id="<?= /* @escapeNotVerified */ $_jsObjectName ?>" <?= /* @escapeNotVerified */ $_element->serialize(['class']) ?> class="fieldset"> - <legend class="legend"><span><?= /* @escapeNotVerified */ $_element->getLegend() ?></span></legend> + <fieldset id="<?= $block->escapeHtmlAttr($_jsObjectName) ?>" <?= /* @noEscape */ $_element->serialize(['class']) ?> class="fieldset"> + <legend class="legend"><span><?= $block->escapeHtml($_element->getLegend()) ?></span></legend> <br> - <?php if ($_element->getComment()): ?> + <?php if ($_element->getComment()) : ?> <div class="messages"> <div class="message message-notice"><?= $block->escapeHtml($_element->getComment()) ?></div> </div> @@ -30,9 +28,9 @@ require([ "prototype" ], function(VarienRulesForm){ -window.<?= /* @escapeNotVerified */ $_jsObjectName ?> = new VarienRulesForm('<?= /* @escapeNotVerified */ $_jsObjectName ?>', '<?= /* @escapeNotVerified */ $block->getNewChildUrl() ?>'); -<?php if ($_element->getReadonly()): ?> - <?= $_element->getHtmlId() ?>.setReadonly(true); +window.<?= /* @noEscape */ $_jsObjectName ?> = new VarienRulesForm('<?= /* @noEscape */ $_jsObjectName ?>', '<?= /* @noEscape */ $block->getNewChildUrl() ?>'); +<?php if ($_element->getReadonly()) : ?> + <?= /* @noEscape */ $_element->getHtmlId() ?>.setReadonly(true); <?php endif; ?> }); diff --git a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/form.phtml b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/form.phtml index 1572c8f0d4e48..5aaa22305a144 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/form.phtml +++ b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/form.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div class="entry-edit rule-tree"> <?= $block->getFormHtml() ?> diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml index fb34a3ac4bb3b..c114f6b1d77cd 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml +++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml @@ -136,7 +136,7 @@ </validation> <dataType>number</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> <description>What is this?</description> </tooltip> <label translate="true">Websites</label> @@ -307,6 +307,7 @@ <settings> <validation> <rule name="required-entry" xsi:type="boolean">true</rule> + <rule name="validate-number-range" xsi:type="string">0.00-100.00</rule> </validation> <dataType>text</dataType> <label translate="true">Discount Amount</label> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml new file mode 100644 index 0000000000000..4345d575a3320 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup"> + <arguments> + <argument name="option" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="seeProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml new file mode 100644 index 0000000000000..56480583af7b4 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <seeInTitle userInput="{{productName}}" stepKey="assertProductNameTitle"/> + <see userInput="{{productName}}" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertProductName"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="assertProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml new file mode 100644 index 0000000000000..3e700b5bcfb1b --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog rule for configurable product with assigned simple products"/> + <description value="Admin should be able to apply catalog rule for configurable product with assigned simple products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14063"/> + <group value="catalogRuleConfigurable"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <!-- Create category for first configurable product --> + <createData entity="SimpleSubCategory" stepKey="firstSimpleCategory"/> + + <!-- Create first configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createFirstConfigProduct"> + <requiredEntity createDataKey="firstSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createFirstConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createFirstConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createFirstConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addFirstProductToAttributeSet"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getFirstConfigAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getFirstConfigAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + + <!-- Create two child products for first configurable product --> + <createData entity="ApiSimpleOne" stepKey="createFirstConfigFirstChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createFirstConfigSecondChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createFirstConfigProductOption"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddFirstChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddSecondChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to first product --> + <updateData createDataKey="createFirstConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateFirstProductWithOption"/> + + <!-- Create category for second configurable product --> + <createData entity="SimpleSubCategory" stepKey="secondSimpleCategory"/> + + <!-- Create second configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createSecondConfigProduct"> + <requiredEntity createDataKey="secondSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createSecondConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createSecondConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createSecondConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addSecondProductToAttributeSet"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getSecondConfigAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getSecondConfigAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + + <!-- Create two child products for second configurable product --> + <createData entity="ApiSimpleOne" stepKey="createSecondConfigFirstChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createSecondConfigSecondChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createSecondConfigProductOption"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddFirstChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddSecondChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to second product --> + <updateData createDataKey="createSecondConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateSecondProductWithOption"/> + + <!--Create customer group --> + <createData entity="CustomCustomerGroup" stepKey="customerGroup"/> + + <!-- Create Customer --> + <createData entity="SimpleUsCustomerWithNewCustomerGroup" stepKey="createCustomer"> + <requiredEntity createDataKey="customerGroup" /> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <!-- Customer log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created data --> + <deleteData createDataKey="createFirstConfigProduct" stepKey="deleteFirstConfigProduct"/> + <deleteData createDataKey="createFirstConfigFirstChildProduct" stepKey="deleteFirstConfigFirstChildProduct"/> + <deleteData createDataKey="createFirstConfigSecondChildProduct" stepKey="deleteFirstConfigSecondChildProduct"/> + <deleteData createDataKey="createFirstConfigProductAttribute" stepKey="deleteFirstConfigProductAttribute"/> + <deleteData createDataKey="firstSimpleCategory" stepKey="deleteFirstSimpleCategory"/> + + <deleteData createDataKey="createSecondConfigProduct" stepKey="deleteSecondConfigProduct"/> + <deleteData createDataKey="createSecondConfigFirstChildProduct" stepKey="deleteSecondConfigFirstChildProduct"/> + <deleteData createDataKey="createSecondConfigSecondChildProduct" stepKey="deleteSecondConfigSecondChildProduct"/> + <deleteData createDataKey="createSecondConfigProductAttribute" stepKey="deleteSecondConfigProductAttribute"/> + <deleteData createDataKey="secondSimpleCategory" stepKey="deleteSimpleCategory"/> + + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> + + <!-- Delete created price rules --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForFirstOption"> + <argument name="name" value="{{CatalogRuleToFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create price rule --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createPriceRule"> + <argument name="catalogRule" value="CatalogRuleToFixed"/> + </actionGroup> + <actionGroup ref="CatalogSelectCustomerGroupActionGroup" stepKey="addCustomerGroup"> + <argument name="customerGroupName" value="$$customerGroup.code$$"/> + </actionGroup> + + <!-- Save price rule --> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="clickSaveAndApplyRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessage"/> + + <!-- Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Login to storefront from customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="logInFromCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Assert first product in category --> + <amOnPage url="$$firstSimpleCategory.name$$.html" stepKey="goToFirstCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForFirstCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkFirstProductPriceInCategory"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert second product in category --> + <amOnPage url="$$secondSimpleCategory.name$$.html" stepKey="goToSecondCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForSecondCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkSecondProductPriceInCategory"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert first product in storefront product page --> + <amOnPage url="$$createFirstConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnFirstProductPage"/> + <waitForPageLoad stepKey="waitForFirstProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkFirstProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add first product with selected options to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addFirstProductWithSelectedOptionToCart1"> + <argument name="product" value="$$createFirstConfigProduct$$"/> + <argument name="option" value="$$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.updatedPrice}}" stepKey="grabForthProductUpdatedPrice1"/> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addFirstProductWithSelectedOptionToCart2"> + <argument name="product" value="$$createFirstConfigProduct$$"/> + <argument name="option" value="$$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + + <!-- Assert second product in storefront product page --> + <amOnPage url="$$createSecondConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnSecondProductPage"/> + <waitForPageLoad stepKey="waitForSecondProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkSecondProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add second product with selected options to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addSecondProductWithSelectedOptionToCart1"> + <argument name="product" value="$$createSecondConfigProduct$$"/> + <argument name="option" value="$$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addSecondProductWithSelectedOptionToCart2"> + <argument name="product" value="$$createSecondConfigProduct$$"/> + <argument name="option" value="$$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + + <!--Assert products prices in the cart --> + <amOnPage url="/checkout/cart/" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForSecondProductOption"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForSecondProductOption"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml new file mode 100644 index 0000000000000..e53e51c626aa9 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminApplyCatalogRuleForConfigurableProductWithOptionsTest"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog price rule for configurable product with options"/> + <description value="Admin should be able to apply the catalog rule for configurable product with options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14062"/> + <group value="catalogRuleConfigurable"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="simpleCategory"/> + + <!-- Create configurable product with three options --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="simpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create three child products --> + <createData entity="ApiSimpleOne" stepKey="createConfigFirstChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigSecondChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigThirdChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddFirstChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddSecondChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigSecondChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddThirdChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigThirdChildProduct"/> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <!-- Delete created data --> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createConfigThirdChildProduct" stepKey="deleteThirdSimpleProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> + + <!-- Delete created price rules --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForFirstOption"> + <argument name="name" value="{{CatalogRuleToFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForSecondOption"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForThirdOption"> + <argument name="name" value="{{CatalogRuleWithoutDiscount.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create price rule for first configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createFirstPriceRule"> + <argument name="catalogRule" value="CatalogRuleToFixed"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForFirstPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createFirstCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyFirstPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForFirstPriceRule"/> + + <!-- Create price rule for second configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createSecondPriceRule"> + <argument name="catalogRule" value="_defaultCatalogRule"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForSecondPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createSecondCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplySecondPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForSecondPriceRule"/> + + <!-- Create price rule for third configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createThirdPriceRule"> + <argument name="catalogRule" value="CatalogRuleWithoutDiscount"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForThirdPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createThirdCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyThirdPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForThirdPriceRule"/> + + <!-- Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Assert product in storefront product page --> + <amOnPage url="$$createConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="assertUpdatedProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert product options price in storefront product page --> + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToFirstProductOption"> + <argument name="option" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToSecondProductOption"> + <argument name="option" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="$110.70"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToThirdProductOption"> + <argument name="option" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{{ApiConfigurableProduct.price}}"/> + </actionGroup> + + <!-- Add product with selected option to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedFirstOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedSecondOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedThirdOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <!--Assert product price in the cart --> + <amOnPage url="/checkout/cart/" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see userInput="{{CatalogRuleToFixed.discount_amount}}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForFirstProductOption"/> + <see userInput="$110.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForSecondProductOption"/> + <see userInput="{{ApiConfigurableProduct.price}}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForThirdProductOption"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index 969688cf5c607..c24717d78d5a7 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/magento-composer-installer": "*", "magento/module-catalog": "103.0.*", @@ -28,5 +28,5 @@ "Magento\\CatalogRuleConfigurable\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php index 01a36117bd1a1..cd2529a8fd725 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php @@ -7,14 +7,9 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; -use Magento\CatalogInventory\Api\Data\StockStatusInterface; -use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockStatusCriteriaInterface; -use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; use Magento\Store\Model\Store; -use Magento\Framework\App\ObjectManager; /** * Catalog search full test search data provider. @@ -129,16 +124,6 @@ class DataProvider */ private $antiGapMultiplier; - /** - * @var StockConfigurationInterface - */ - private $stockConfiguration; - - /** - * @var StockStatusRepositoryInterface - */ - private $stockStatusRepository; - /** * @param ResourceConnection $resource * @param \Magento\Catalog\Model\Product\Type $catalogProductType @@ -563,8 +548,6 @@ public function prepareProductIndex($indexData, $productData, $storeId) { $index = []; - $indexData = $this->filterOutOfStockProducts($indexData, $storeId); - foreach ($this->getSearchableAttributes('static') as $attribute) { $attributeCode = $attribute->getAttributeCode(); @@ -590,11 +573,10 @@ public function prepareProductIndex($indexData, $productData, $storeId) foreach ($attributeData as $attributeId => $attributeValues) { $value = $this->getAttributeValue($attributeId, $attributeValues, $storeId); if (!empty($value)) { - if (isset($index[$attributeId])) { - $index[$attributeId][$entityId] = $value; - } else { - $index[$attributeId] = [$entityId => $value]; + if (!isset($index[$attributeId])) { + $index[$attributeId] = []; } + $index[$attributeId][$entityId] = $value; } } } @@ -662,9 +644,12 @@ private function getAttributeOptionValue($attributeId, $valueIds, $storeId) $attribute->setStoreId($storeId); $options = $attribute->getSource()->toOptionArray(); $this->attributeOptions[$optionKey] = array_column($options, 'label', 'value'); - $this->attributeOptions[$optionKey] = array_map(function ($value) { - return $this->filterAttributeValue($value); - }, $this->attributeOptions[$optionKey]); + $this->attributeOptions[$optionKey] = array_map( + function ($value) { + return $this->filterAttributeValue($value); + }, + $this->attributeOptions[$optionKey] + ); } else { $this->attributeOptions[$optionKey] = null; } @@ -687,65 +672,4 @@ private function filterAttributeValue($value) { return preg_replace('/\s+/iu', ' ', trim(strip_tags($value))); } - - /** - * Filter out of stock products for products. - * - * @param array $indexData - * @param int $storeId - * @return array - */ - private function filterOutOfStockProducts($indexData, $storeId): array - { - if (!$this->getStockConfiguration()->isShowOutOfStock($storeId)) { - $productIds = array_keys($indexData); - $stockStatusCriteria = $this->createStockStatusCriteria(); - $stockStatusCriteria->setProductsFilter($productIds); - $stockStatusCollection = $this->getStockStatusRepository()->getList($stockStatusCriteria); - $stockStatuses = $stockStatusCollection->getItems(); - $stockStatuses = array_filter($stockStatuses, function (StockStatusInterface $stockStatus) { - return StockStatusInterface::STATUS_IN_STOCK == $stockStatus->getStockStatus(); - }); - $indexData = array_intersect_key($indexData, $stockStatuses); - } - return $indexData; - } - - /** - * Get stock configuration. - * - * @return StockConfigurationInterface - */ - private function getStockConfiguration() - { - if (null === $this->stockConfiguration) { - $this->stockConfiguration = ObjectManager::getInstance()->get(StockConfigurationInterface::class); - } - return $this->stockConfiguration; - } - - /** - * Create stock status criteria. - * - * Substitution of autogenerated factory in backward compatibility reasons. - * - * @return StockStatusCriteriaInterface - */ - private function createStockStatusCriteria() - { - return ObjectManager::getInstance()->create(StockStatusCriteriaInterface::class); - } - - /** - * Get stock status repository. - * - * @return StockStatusRepositoryInterface - */ - private function getStockStatusRepository() - { - if (null === $this->stockStatusRepository) { - $this->stockStatusRepository = ObjectManager::getInstance()->get(StockStatusRepositoryInterface::class); - } - return $this->stockStatusRepository; - } } diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php b/app/code/Magento/CatalogSearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php new file mode 100644 index 0000000000000..02e48c5d8a1c0 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Plugin/StockedProductsFilterPlugin.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Indexer\Plugin; + +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; +use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; +use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; + +/** + * Plugin for filtering child products that are out of stock for preventing their saving to catalog search index. + * + * This plugin reverts changes introduced in commit 9ab466d8569ea556cb01393989579c3aac53d9a3 which break extensions + * relying on stocks. Plugin location is changed for consistency purposes. + */ +class StockedProductsFilterPlugin +{ + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @var StockStatusRepositoryInterface + */ + private $stockStatusRepository; + + /** + * @var StockStatusCriteriaInterfaceFactory + */ + private $stockStatusCriteriaFactory; + + /** + * @param StockConfigurationInterface $stockConfiguration + * @param StockStatusRepositoryInterface $stockStatusRepository + * @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory + */ + public function __construct( + StockConfigurationInterface $stockConfiguration, + StockStatusRepositoryInterface $stockStatusRepository, + StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory + ) { + $this->stockConfiguration = $stockConfiguration; + $this->stockStatusRepository = $stockStatusRepository; + $this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory; + } + + /** + * Filter out of stock options for configurable product. + * + * @param DataProvider $dataProvider + * @param array $indexData + * @param array $productData + * @param int $storeId + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforePrepareProductIndex( + DataProvider $dataProvider, + array $indexData, + array $productData, + int $storeId + ): array { + if (!$this->stockConfiguration->isShowOutOfStock($storeId)) { + $productIds = array_keys($indexData); + $stockStatusCriteria = $this->stockStatusCriteriaFactory->create(); + $stockStatusCriteria->setProductsFilter($productIds); + $stockStatusCollection = $this->stockStatusRepository->getList($stockStatusCriteria); + $stockStatuses = $stockStatusCollection->getItems(); + $stockStatuses = array_filter( + $stockStatuses, + function (StockStatusInterface $stockStatus) { + return StockStatusInterface::STATUS_IN_STOCK == $stockStatus->getStockStatus(); + } + ); + $indexData = array_intersect_key($indexData, $stockStatuses); + } + + return [ + $indexData, + $productData, + $storeId, + ]; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index ddfedad6927f4..5200056e5596e 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -355,9 +355,11 @@ protected function _renderFiltersBefore() */ private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface { - return $this->totalRecordsResolverFactory->create([ + return $this->totalRecordsResolverFactory->create( + [ 'searchResult' => $searchResult, - ]); + ] + ); } /** @@ -367,14 +369,16 @@ private function getTotalRecordsResolver(SearchResultInterface $searchResult): T */ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface { - return $this->searchCriteriaResolverFactory->create([ + return $this->searchCriteriaResolverFactory->create( + [ 'builder' => $this->getSearchCriteriaBuilder(), 'collection' => $this, 'searchRequestName' => $this->searchRequestName, 'currentPage' => $this->_curPage, 'size' => $this->getPageSize(), 'orders' => $this->searchOrders, - ]); + ] + ); } /** @@ -385,12 +389,14 @@ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface */ private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface { - return $this->searchResultApplierFactory->create([ + return $this->searchResultApplierFactory->create( + [ 'collection' => $this, 'searchResult' => $searchResult, /** This variable sets by serOrder method, but doesn't have a getter method. */ 'orders' => $this->_orders - ]); + ] + ); } /** diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 9d0d4c32cfb27..057b1755f25ce 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -470,9 +470,11 @@ private function setSearchOrder($field, $direction) */ private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface { - return $this->totalRecordsResolverFactory->create([ + return $this->totalRecordsResolverFactory->create( + [ 'searchResult' => $searchResult, - ]); + ] + ); } /** @@ -482,14 +484,16 @@ private function getTotalRecordsResolver(SearchResultInterface $searchResult): T */ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface { - return $this->searchCriteriaResolverFactory->create([ + return $this->searchCriteriaResolverFactory->create( + [ 'builder' => $this->getSearchCriteriaBuilder(), 'collection' => $this, 'searchRequestName' => $this->searchRequestName, 'currentPage' => $this->_curPage, 'size' => $this->getPageSize(), 'orders' => $this->searchOrders, - ]); + ] + ); } /** @@ -500,12 +504,14 @@ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface */ private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface { - return $this->searchResultApplierFactory->create([ + return $this->searchResultApplierFactory->create( + [ 'collection' => $this, 'searchResult' => $searchResult, /** This variable sets by serOrder method, but doesn't have a getter method. */ 'orders' => $this->_orders, - ]); + ] + ); } /** diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php index 0adc2fcecbfa7..8f8ba39ebd329 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php @@ -103,7 +103,7 @@ private function generateRequest($attributeType, $container, $useFulltext) } } /** @var $attribute Attribute */ - if (!$attribute->getIsSearchable() || in_array($attribute->getAttributeCode(), ['price', 'sku'], true)) { + if (!$attribute->getIsSearchable() || in_array($attribute->getAttributeCode(), ['price'], true)) { // Some fields have their own specific handlers continue; } diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml index 067d76821d687..34e86566d73ba 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml @@ -64,6 +64,13 @@ <see selector="{{StorefrontQuickSearchResultsSection.productByIndex(index)}}" userInput="{{productName}}" stepKey="seeProductName"/> </actionGroup> + <actionGroup name="StorefrontQuickSearchSeeProductByName"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <see selector="{{StorefrontQuickSearchResultsSection.productByName(productName)}}" userInput="{{productName}}" stepKey="seeProductName"/> + </actionGroup> + <actionGroup name="StorefrontQuickSearchCheckProductNameNotInGrid"> <arguments> <argument name="productName" type="string"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml index ac316d060f6e9..aa0145b9f96cd 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml @@ -19,5 +19,7 @@ <element name="searchTermRowCheckboxBySearchQuery" type="checkbox" selector="//*[normalize-space()='{{var1}}']/preceding-sibling::td//input[@name='search']" parameterized="true" timeout="30"/> <element name="okButton" type="button" selector="//button[@class='action-primary action-accept']/span" timeout="30"/> <element name="emptyRecords" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td[@class='empty-text']"/> + <element name="gridRow" type="text" selector="//tr[@data-role='row']"/> + <element name="numberOfSearchTermResults" type="text" selector="//tr[@data-role='row']/td[@data-column='num_results']"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml index a7d577a7508c0..a07e2a9b8a547 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml @@ -9,8 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCatalogSearchTermNewSection"> - <element name="searchQuery" type="text" selector="//div[@class='admin__field-control control']/input[@id='query_text']"/> - <element name="store" type="text" selector="//select[@id='store_id']"/> + <element name="searchQuery" type="text" selector="#query_text"/> + <element name="store" type="text" selector="#store_id"/> + <element name="numberOfResults" type="button" selector="#num_results"/> + <element name="numberOfUses" type="button" selector="#popularity"/> <element name="redirectUrl" type="text" selector="//div[@class='admin__field-control control']/input[@id='redirect']"/> <element name="displayInSuggestedTerm" type="select" selector="//select[@name='display_in_terms']"/> <element name="saveSearchButton" type="button" selector="//button[@id='save']/span[@class='ui-button-text']" timeout="30"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml index 2fea5c988bf9d..b6417e12a6db7 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml @@ -11,6 +11,7 @@ <test name="MinimalQueryLengthForCatalogSearchTest"> <annotations> <features value="CatalogSearch"/> + <stories value="Catalog search"/> <title value="Minimal query length for catalog search"/> <description value="Minimal query length for catalog search"/> <severity value="AVERAGE"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml index 19db201e91f40..9a2103ee75a7a 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml @@ -368,6 +368,7 @@ <group value="mtf_migrated"/> </annotations> <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> <createData entity="_defaultCategory" stepKey="createCategory"/> <createData entity="DownloadableProductWithOneLink" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> @@ -377,6 +378,7 @@ </createData> </before> <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> </after> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml new file mode 100644 index 0000000000000..3ebb09f3c9c26 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUpdateSearchTermEntityTest"> + <annotations> + <stories value="Storefront Search"/> + <title value="Update Storefront Search Results"/> + <description value="You should see the updated Search Term on the Storefront via the Admin."/> + <testCaseId value="MC-13987"/> + <severity value="CRITICAL"/> + <group value="search"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory1"/> + <createData entity="SimpleProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> + + <deleteData createDataKey="createProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createCategory1" stepKey="deleteCategory1"/> + </after> + + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> + <argument name="phrase" value="$$createProduct1.name$$"/> + </actionGroup> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByFirstSearchQuery1"> + <argument name="searchQuery" value="$$createProduct1.name$$"/> + </actionGroup> + + <click selector="{{AdminGridRow.editByValue($$createProduct1.name$$)}}" stepKey="clickOnSearchResult1"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + + <actionGroup ref="AdminFillAllSearchTermFieldsActionGroup" stepKey="searchForSearchTerm1"> + <argument name="searchTerm" value="UpdatedSearchTermData1"/> + </actionGroup> + + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage2"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByFirstSearchQuery2"> + <argument name="searchQuery" value="{{UpdatedSearchTermData1.query_text}}"/> + </actionGroup> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage2"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName2"> + <argument name="phrase" value="{{UpdatedSearchTermData1.query_text}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php new file mode 100644 index 0000000000000..b9909ec2c74b2 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Plugin/StockedProductsFilterPluginTest.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Test\Unit\Model\Indexer\Plugin; + +use Magento\CatalogSearch\Model\Indexer\Plugin\StockedProductsFilterPlugin; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; +use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\StockStatusCriteriaInterface; +use Magento\CatalogInventory\Api\Data\StockStatusCollectionInterface; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; + +/** + * Test for Magento\CatalogSearch\Model\Indexer\Plugin\StockedProductsFilterPlugin class. + * + * This plugin reverts changes introduced in commit 9ab466d8569ea556cb01393989579c3aac53d9a3 which break extensions + * relying on stocks. Plugin location is changed for consistency purposes. + */ +class StockedProductsFilterPluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var StockConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $stockConfigurationMock; + + /** + * @var StockStatusRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $stockStatusRepositoryMock; + + /** + * @var StockStatusCriteriaInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $stockStatusCriteriaFactoryMock; + + /** + * @var StockedProductsFilterPlugin + */ + private $plugin; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->stockConfigurationMock = $this->getMockBuilder(StockConfigurationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockStatusRepositoryMock = $this->getMockBuilder(StockStatusRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockStatusCriteriaFactoryMock = $this->getMockBuilder(StockStatusCriteriaInterfaceFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new StockedProductsFilterPlugin( + $this->stockConfigurationMock, + $this->stockStatusRepositoryMock, + $this->stockStatusCriteriaFactoryMock + ); + } + + /** + * @return void + */ + public function testBeforePrepareProductIndex(): void + { + /** @var DataProvider|\PHPUnit_Framework_MockObject_MockObject $dataProviderMock */ + $dataProviderMock = $this->getMockBuilder(DataProvider::class)->disableOriginalConstructor()->getMock(); + $indexData = [ + 1 => [], + 2 => [], + ]; + $productData = []; + $storeId = 1; + + $this->stockConfigurationMock + ->expects($this->once()) + ->method('isShowOutOfStock') + ->willReturn(false); + + $stockStatusCriteriaMock = $this->getMockBuilder(StockStatusCriteriaInterface::class)->getMock(); + $stockStatusCriteriaMock + ->expects($this->once()) + ->method('setProductsFilter') + ->willReturn(true); + $this->stockStatusCriteriaFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($stockStatusCriteriaMock); + + $stockStatusMock = $this->getMockBuilder(StockStatusInterface::class)->getMock(); + $stockStatusMock->expects($this->atLeastOnce()) + ->method('getStockStatus') + ->willReturnOnConsecutiveCalls(Stock::STOCK_IN_STOCK, Stock::STOCK_OUT_OF_STOCK); + $stockStatusCollectionMock = $this->getMockBuilder(StockStatusCollectionInterface::class)->getMock(); + $stockStatusCollectionMock + ->expects($this->once()) + ->method('getItems') + ->willReturn([1 => $stockStatusMock, 2 => $stockStatusMock]); + $this->stockStatusRepositoryMock + ->expects($this->once()) + ->method('getList') + ->willReturn($stockStatusCollectionMock); + + list ($indexData, $productData, $storeId) = $this->plugin->beforePrepareProductIndex( + $dataProviderMock, + $indexData, + $productData, + $storeId + ); + + $this->assertEquals([1], array_keys($indexData)); + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGeneratorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGeneratorTest.php index b52c9cfd67494..a8c654652a32f 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGeneratorTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGeneratorTest.php @@ -9,6 +9,9 @@ use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorResolver; use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorInterface; +/** + * Test for \Magento\CatalogSearch\Model\Search\RequestGenerator + */ class RequestGeneratorTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ @@ -61,7 +64,7 @@ public function attributesProvider() return [ [ [ - 'quick_search_container' => ['queries' => 0, 'filters' => 0, 'aggregations' => 0], + 'quick_search_container' => ['queries' => 1, 'filters' => 0, 'aggregations' => 0], 'advanced_search_container' => ['queries' => 0, 'filters' => 0, 'aggregations' => 0], 'catalog_view_container' => ['queries' => 0, 'filters' => 0, 'aggregations' => 0] ], diff --git a/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php b/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php index 1b05152903aa9..f312178e0bf0b 100644 --- a/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php +++ b/app/code/Magento/CatalogSearch/Ui/DataProvider/Product/AddFulltextFilterToCollection.php @@ -5,8 +5,8 @@ */ namespace Magento\CatalogSearch\Ui\DataProvider\Product; -use Magento\Framework\Data\Collection; use Magento\CatalogSearch\Model\ResourceModel\Search\Collection as SearchCollection; +use Magento\Framework\Data\Collection; use Magento\Ui\DataProvider\AddFilterToCollectionInterface; /** @@ -30,14 +30,14 @@ public function __construct(SearchCollection $searchCollection) } /** - * {@inheritdoc} + * @inheritdoc * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function addFilter(Collection $collection, $field, $condition = null) { /** @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ - if (isset($condition['fulltext']) && !empty($condition['fulltext'])) { + if (isset($condition['fulltext']) && (string)$condition['fulltext'] !== '') { $this->searchCollection->addBackendSearchFilter($condition['fulltext']); $productIds = $this->searchCollection->load()->getAllIds(); $collection->addIdFilter($productIds); diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 56a28e5337f3e..0c2269174d87d 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -35,5 +35,5 @@ "Magento\\CatalogSearch\\": "" } }, - "version": "101.0.2" + "version": "101.0.3" } diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index 28d5035308dee..63de2dc00926b 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -373,4 +373,7 @@ </argument> </arguments> </type> + <type name="Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider"> + <plugin name="stockedProductsFilterPlugin" type="Magento\CatalogSearch\Model\Indexer\Plugin\StockedProductsFilterPlugin"/> + </type> </config> diff --git a/app/code/Magento/CatalogSearch/etc/search_request.xml b/app/code/Magento/CatalogSearch/etc/search_request.xml index d7bfb2e6b4a5c..6f9eb6e20666e 100644 --- a/app/code/Magento/CatalogSearch/etc/search_request.xml +++ b/app/code/Magento/CatalogSearch/etc/search_request.xml @@ -19,7 +19,6 @@ <queryReference clause="must" ref="visibility"/> </query> <query xsi:type="matchQuery" value="$search_term$" name="search"> - <match field="sku"/> <match field="*"/> </query> <query xsi:type="filteredQuery" name="category"> diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml index 95ea7fcef3a1a..3712f221233ee 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml @@ -4,8 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis ?> <?php /** @@ -14,108 +13,120 @@ * @var $block \Magento\CatalogSearch\Block\Advanced\Form */ ?> -<?php $maxQueryLength = $this->helper('Magento\CatalogSearch\Helper\Data')->getMaxQueryLength();?> -<form class="form search advanced" action="<?= /* @escapeNotVerified */ $block->getSearchPostUrl() ?>" method="get" id="form-validate"> +<?php $maxQueryLength = $this->helper(\Magento\CatalogSearch\Helper\Data::class)->getMaxQueryLength();?> +<form class="form search advanced" action="<?= $block->escapeUrl($block->getSearchPostUrl()) ?>" method="get" id="form-validate"> <fieldset class="fieldset"> - <legend class="legend"><span><?= /* @escapeNotVerified */ __('Search Settings') ?></span></legend><br /> - <?php foreach ($block->getSearchableAttributes() as $_attribute): ?> - <?php $_code = $_attribute->getAttributeCode() ?> - <div class="field <?= /* @escapeNotVerified */ $_code ?>"> - <label class="label" for="<?= /* @escapeNotVerified */ $_code ?>"> + <legend class="legend"><span><?= $block->escapeHtml(__('Search Settings')) ?></span></legend><br /> + <?php foreach ($block->getSearchableAttributes() as $_attribute) : ?> + <?php $_code = $_attribute->getAttributeCode() ?> + <div class="field <?= $block->escapeHtmlAttr($_code) ?>"> + <label class="label" for="<?= $block->escapeHtmlAttr($_code) ?>"> <span><?= $block->escapeHtml(__($block->getAttributeLabel($_attribute))) ?></span> </label> <div class="control"> - <?php switch ($block->getAttributeInputType($_attribute)): - case 'number': ?> + <?php + switch ($block->getAttributeInputType($_attribute)) : + case 'number': + ?> <div class="range fields group group-2"> <div class="field no-label"> <div class="control"> <input type="text" - name="<?= /* @escapeNotVerified */ $_code ?>[from]" + name="<?= $block->escapeHtmlAttr($_code) ?>[from]" value="<?= $block->escapeHtml($block->getAttributeValue($_attribute, 'from')) ?>" - id="<?= /* @escapeNotVerified */ $_code ?>" + id="<?= $block->escapeHtmlAttr($_code) ?>" title="<?= $block->escapeHtml($block->getAttributeLabel($_attribute)) ?>" class="input-text" - maxlength="<?= /* @escapeNotVerified */ $maxQueryLength ?>" - data-validate="{number:true, 'less-than-equals-to':'#<?= /* @escapeNotVerified */ $_code ?>_to'}" /> + maxlength="<?= $block->escapeHtmlAttr($maxQueryLength) ?>" + data-validate="{number:true, 'less-than-equals-to':'#<?= $block->escapeHtmlAttr($_code) ?>_to'}" /> </div> </div> <div class="field no-label"> <div class="control"> <input type="text" - name="<?= /* @escapeNotVerified */ $_code ?>[to]" + name="<?= $block->escapeHtmlAttr($_code) ?>[to]" value="<?= $block->escapeHtml($block->getAttributeValue($_attribute, 'to')) ?>" - id="<?= /* @escapeNotVerified */ $_code ?>_to" + id="<?= $block->escapeHtmlAttr($_code) ?>_to" title="<?= $block->escapeHtml($block->getAttributeLabel($_attribute)) ?>" class="input-text" - maxlength="<?= /* @escapeNotVerified */ $maxQueryLength ?>" - data-validate="{number:true, 'greater-than-equals-to':'#<?= /* @escapeNotVerified */ $_code ?>'}" /> + maxlength="<?= $block->escapeHtmlAttr($maxQueryLength) ?>" + data-validate="{number:true, 'greater-than-equals-to':'#<?= $block->escapeHtmlAttr($_code) ?>'}" /> </div> </div> </div> - <?php break; - case 'price': ?> + <?php + break; + case 'price': + ?> <div class="range price fields group group-2"> <div class="field no-label"> <div class="control"> - <input name="<?= /* @escapeNotVerified */ $_code ?>[from]" + <input name="<?= $block->escapeHtmlAttr($_code) ?>[from]" value="<?= $block->escapeHtml($block->getAttributeValue($_attribute, 'from')) ?>" - id="<?= /* @escapeNotVerified */ $_code ?>" + id="<?= $block->escapeHtmlAttr($_code) ?>" title="<?= $block->escapeHtml($block->getAttributeLabel($_attribute)) ?>" class="input-text" type="text" - maxlength="<?= /* @escapeNotVerified */ $maxQueryLength ?>" - data-validate="{number:true, 'less-than-equals-to':'#<?= /* @escapeNotVerified */ $_code ?>_to'}" /> + maxlength="<?= $block->escapeHtmlAttr($maxQueryLength) ?>" + data-validate="{number:true, 'less-than-equals-to':'#<?= $block->escapeHtmlAttr($_code) ?>_to'}" /> </div> </div> <div class="field with-addon no-label"> <div class="control"> <div class="addon"> - <input name="<?= /* @escapeNotVerified */ $_code ?>[to]" + <input name="<?= $block->escapeHtmlAttr($_code) ?>[to]" value="<?= $block->escapeHtml($block->getAttributeValue($_attribute, 'to')) ?>" - id="<?= /* @escapeNotVerified */ $_code ?>_to" + id="<?= $block->escapeHtmlAttr($_code) ?>_to" title="<?= $block->escapeHtml($block->getAttributeLabel($_attribute)) ?>" class="input-text" type="text" - maxlength="<?= /* @escapeNotVerified */ $maxQueryLength ?>" - data-validate="{number:true, 'greater-than-equals-to':'#<?= /* @escapeNotVerified */ $_code ?>'}" /> + maxlength="<?= $block->escapeHtmlAttr($maxQueryLength) ?>" + data-validate="{number:true, 'greater-than-equals-to':'#<?= $block->escapeHtmlAttr($_code) ?>'}" /> <label class="addafter" - for="<?= /* @escapeNotVerified */ $_code ?>_to"> - <?= /* @escapeNotVerified */ $block->getCurrency($_attribute) ?> + for="<?= $block->escapeHtmlAttr($_code) ?>_to"> + <?= $block->escapeHtml($block->getCurrency($_attribute)) ?> </label> </div> </div> </div> </div> - <?php break; - case 'select': ?> - <?= /* @escapeNotVerified */ $block->getAttributeSelectElement($_attribute) ?> - <?php break; - case 'yesno': ?> - <?= /* @escapeNotVerified */ $block->getAttributeYesNoElement($_attribute) ?> - <?php break; - case 'date': ?> + <?php + break; + case 'select': + ?> + <?= /* @noEscape */ $block->getAttributeSelectElement($_attribute) ?> + <?php + break; + case 'yesno': + ?> + <?= /* @noEscape */ $block->getAttributeYesNoElement($_attribute) ?> + <?php + break; + case 'date': + ?> <div class="range dates fields group group-2"> <div class="field date no-label"> <div class="control"> - <?= /* @escapeNotVerified */ $block->getDateInput($_attribute, 'from') ?> + <?= /* @noEscape */ $block->getDateInput($_attribute, 'from') ?> </div> </div> <div class="field date no-label"> <div class="control"> - <?= /* @escapeNotVerified */ $block->getDateInput($_attribute, 'to') ?> + <?= /* @noEscape */ $block->getDateInput($_attribute, 'to') ?> </div> </div> </div> - <?php break; - default: ?> + <?php + break; + default: + ?> <input type="text" - name="<?= /* @escapeNotVerified */ $_code ?>" - id="<?= /* @escapeNotVerified */ $_code ?>" + name="<?= $block->escapeHtmlAttr($_code) ?>" + id="<?= $block->escapeHtmlAttr($_code) ?>" value="<?= $block->escapeHtml($block->getAttributeValue($_attribute)) ?>" title="<?= $block->escapeHtml($block->getAttributeLabel($_attribute)) ?>" - class="input-text <?= /* @escapeNotVerified */ $block->getAttributeValidationClass($_attribute) ?>" - maxlength="<?= /* @escapeNotVerified */ $maxQueryLength ?>" /> + class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass($_attribute)) ?>" + maxlength="<?= $block->escapeHtmlAttr($maxQueryLength) ?>" /> <?php endswitch; ?> </div> </div> @@ -126,7 +137,7 @@ <button type="submit" class="action search primary" title="<?= $block->escapeHtml(__('Search')) ?>"> - <span><?= /* @escapeNotVerified */ __('Search') ?></span> + <span><?= $block->escapeHtml(__('Search')) ?></span> </button> </div> </div> @@ -147,8 +158,8 @@ require([ } }, messages: { - 'price[to]': {'greater-than-equals-to': '<?= /* @escapeNotVerified */ __('Please enter a valid price range.') ?>'}, - 'price[from]': {'less-than-equals-to': '<?= /* @escapeNotVerified */ __('Please enter a valid price range.') ?>'} + 'price[to]': {'greater-than-equals-to': '<?= $block->escapeJs(__('Please enter a valid price range.')) ?>'}, + 'price[from]': {'less-than-equals-to': '<?= $block->escapeJs(__('Please enter a valid price range.')) ?>'} } }); }); diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml index 09098b1ccd003..2e183291da778 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var \Magento\CatalogSearch\Helper\Data $helper */ -$helper = $this->helper('Magento\CatalogSearch\Helper\Data'); +$helper = $this->helper(\Magento\CatalogSearch\Helper\Data::class); ?> <div class="nested"> - <a class="action advanced" href="<?= /* @escapeNotVerified */ $helper->getAdvancedSearchUrl() ?>" data-action="advanced-search"> - <?= /* @escapeNotVerified */ __('Advanced Search') ?> + <a class="action advanced" href="<?= $block->escapeUrl($helper->getAdvancedSearchUrl()) ?>" data-action="advanced-search"> + <?= $block->escapeHtml(__('Advanced Search')) ?> </a> </div> diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml index 3f616ab791dfe..e21b031d69521 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -14,43 +11,43 @@ /** this changes need for valid apply filters and configuration before search process is started */ $productList = $block->getProductListHtml(); ?> -<?php if ($results = $block->getResultCount()): ?> +<?php if ($results = $block->getResultCount()) : ?> <div class="search found"> <?php if ($results == 1) : ?> - <?= /* @escapeNotVerified */ __('<strong>%1 item</strong> were found using the following search criteria', $results) ?> - <?php else: ?> - <?= /* @escapeNotVerified */ __('<strong>%1 items</strong> were found using the following search criteria', $results) ?> + <?= /* @noEscape */ __('<strong>%1 item</strong> were found using the following search criteria', $results) ?> + <?php else : ?> + <?= /* @noEscape */ __('<strong>%1 items</strong> were found using the following search criteria', $results) ?> <?php endif; ?> </div> -<?php else: ?> +<?php else : ?> <div role="alert" class="message error"> <div> - <?= /* @escapeNotVerified */ __('We can\'t find any items matching these search criteria.') ?> <a href="<?= /* @escapeNotVerified */ $block->getFormUrl() ?>"><?= /* @escapeNotVerified */ __('Modify your search.') ?></a> + <?= $block->escapeHtml(__('We can\'t find any items matching these search criteria.')) ?> <a href="<?= $block->escapeUrl($block->getFormUrl()) ?>"><?= $block->escapeHtml(__('Modify your search.')) ?></a> </div> </div> <?php endif; ?> <?php $searchCriterias = $block->getSearchCriterias(); ?> <div class="search summary"> - <?php foreach (['left', 'right'] as $side): ?> - <?php if (@$searchCriterias[$side]): ?> + <?php foreach (['left', 'right'] as $side) : ?> + <?php if (!empty($searchCriterias[$side])) : ?> <ul class="items"> - <?php foreach ($searchCriterias[$side] as $criteria): ?> + <?php foreach ($searchCriterias[$side] as $criteria) : ?> <li class="item"><strong><?= $block->escapeHtml(__($criteria['name'])) ?>:</strong> <?= $block->escapeHtml($criteria['value']) ?></li> <?php endforeach; ?> </ul> <?php endif; ?> <?php endforeach; ?> </div> -<?php if ($block->getResultCount()): ?> +<?php if ($block->getResultCount()) : ?> <div class="message notice"> <div> - <?= /* @escapeNotVerified */ __("Don't see what you're looking for?") ?> - <a href="<?= /* @escapeNotVerified */ $block->getFormUrl() ?>"><?= /* @escapeNotVerified */ __('Modify your search.') ?></a> + <?= $block->escapeHtml(__("Don't see what you're looking for?")) ?> + <a href="<?= $block->escapeUrl($block->getFormUrl()) ?>"><?= $block->escapeHtml(__('Modify your search.')) ?></a> </div> </div> <?php endif; ?> -<?php if ($block->getResultCount()): ?> - <div class="search results"><?= /* @escapeNotVerified */ $productList ?></div> +<?php if ($block->getResultCount()) : ?> + <div class="search results"><?= /* @noEscape */ $productList ?></div> <?php endif; ?> <?php $block->getSearchCriterias(); ?> diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/result.phtml index 2757ae3b5f7ed..32b26eec9dbe6 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/result.phtml @@ -4,33 +4,33 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** This changes need to valid applying filters and configuration before search process is started. */ +$productList = $block->getProductListHtml(); ?> -<?php if ($block->getResultCount()): ?> -<?= $block->getChildHtml('tagged_product_list_rss_link') ?> +<?php if ($block->getResultCount()) : ?> + <?= /* @noEscape */ $block->getChildHtml('tagged_product_list_rss_link') ?> <div class="search results"> - <?php if ($messages = $block->getNoteMessages()):?> + <?php if ($messages = $block->getNoteMessages()) : ?> <div class="message notice"> <div> - <?php foreach ($messages as $message):?> - <?= /* @escapeNotVerified */ $message ?><br /> - <?php endforeach;?> + <?php foreach ($messages as $message) : ?> + <?= /* @noEscape */ $message ?><br /> + <?php endforeach; ?> </div> </div> <?php endif; ?> - <?= $block->getProductListHtml() ?> + <?= /* @noEscape */ $productList ?> </div> -<?php else: ?> +<?php else : ?> <div class="message notice"> <div> - <?= /* @escapeNotVerified */ ($block->getNoResultText()) ? $block->getNoResultText() : __('Your search returned no results.') ?> - <?= $block->getAdditionalHtml() ?> - <?php if ($messages = $block->getNoteMessages()):?> - <?php foreach ($messages as $message):?> - <br /><?= /* @escapeNotVerified */ $message ?> - <?php endforeach;?> + <?= $block->escapeHtml($block->getNoResultText() ? $block->getNoResultText() : __('Your search returned no results.')) ?> + <?= /* @noEscape */ $block->getAdditionalHtml() ?> + <?php if ($messages = $block->getNoteMessages()) : ?> + <?php foreach ($messages as $message) : ?> + <br /><?= /* @noEscape */ $message ?> + <?php endforeach; ?> <?php endif; ?> </div> </div> diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/search_terms_log.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/search_terms_log.phtml index 61609bdf66bda..38ef11933a46f 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/search_terms_log.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/search_terms_log.phtml @@ -3,14 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile ?> -<?php if ($block->getSearchTermsLog()->isPageCacheable()): ?> +<?php if ($block->getSearchTermsLog()->isPageCacheable()) : ?> <script type="text/x-magento-init"> { "*": { "Magento_CatalogSearch/js/search-terms-log": { - "url": "<?= /* @escapeNotVerified */ $block->getUrl('catalogsearch/searchTermsLog/save') ?>" + "url": "<?= $block->escapeUrl($block->getUrl('catalogsearch/searchTermsLog/save')) ?>" } } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php index 572152e84b2d7..00bf88675e752 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php @@ -11,6 +11,9 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product; +/** + * Storage Plugin + */ class Storage { /** @@ -36,6 +39,8 @@ public function __construct( } /** + * Save product/category urlRewrite association + * * @param \Magento\UrlRewrite\Model\StorageInterface $object * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] $result * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] $urls @@ -53,13 +58,15 @@ public function afterReplace(StorageInterface $object, array $result, array $url 'product_id' => $record->getEntityId(), ]; } - if ($toSave) { + if (count($toSave) > 0) { $this->productResource->saveMultiple($toSave); } return $result; } /** + * Remove product/category urlRewrite association + * * @param \Magento\UrlRewrite\Model\StorageInterface $object * @param array $data * @return void @@ -71,6 +78,8 @@ public function beforeDeleteByData(StorageInterface $object, array $data) } /** + * Filter urls + * * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] $urls * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ @@ -96,6 +105,8 @@ protected function filterUrls(array $urls) } /** + * Check if url is correct + * * @param UrlRewrite $url * @return bool */ diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php index a7cc894c9a022..4a191b54dea68 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php @@ -13,7 +13,12 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +/** + * Generate url rewrites for anchor categories + */ class AnchorUrlRewriteGenerator { /** @@ -52,7 +57,7 @@ public function __construct( * @param int $storeId * @param Product $product * @param ObjectRegistry $productCategories - * @return UrlRewrite[] + * @return UrlRewrite[]|array */ public function generate($storeId, Product $product, ObjectRegistry $productCategories) { diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php index 9e787e74ae073..a7e4b511ecdd2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php @@ -11,7 +11,12 @@ use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +/** + * Generate url rewrites for categories + */ class CategoriesUrlRewriteGenerator { /** @@ -28,8 +33,10 @@ class CategoriesUrlRewriteGenerator * @param ProductUrlPathGenerator $productUrlPathGenerator * @param UrlRewriteFactory $urlRewriteFactory */ - public function __construct(ProductUrlPathGenerator $productUrlPathGenerator, UrlRewriteFactory $urlRewriteFactory) - { + public function __construct( + ProductUrlPathGenerator $productUrlPathGenerator, + UrlRewriteFactory $urlRewriteFactory + ) { $this->productUrlPathGenerator = $productUrlPathGenerator; $this->urlRewriteFactory = $urlRewriteFactory; } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php index 6b838f83d31e4..9d26184e2c2d4 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php @@ -13,6 +13,7 @@ use Magento\CatalogUrlRewrite\Model\Product\CategoriesUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator; use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; @@ -34,6 +35,11 @@ class ProductScopeRewriteGenerator */ private $storeManager; + /** + * @var ScopeConfigInterface + */ + private $config; + /** * @var ObjectRegistryFactory */ @@ -79,6 +85,8 @@ class ProductScopeRewriteGenerator * @param AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory * @param CategoryRepositoryInterface|null $categoryRepository + * @param ScopeConfigInterface|null $config + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( StoreViewService $storeViewService, @@ -89,7 +97,8 @@ public function __construct( CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator, AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator, MergeDataProviderFactory $mergeDataProviderFactory = null, - CategoryRepositoryInterface $categoryRepository = null + CategoryRepositoryInterface $categoryRepository = null, + ScopeConfigInterface $config = null ) { $this->storeViewService = $storeViewService; $this->storeManager = $storeManager; @@ -104,6 +113,7 @@ public function __construct( $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); $this->categoryRepository = $categoryRepository ?: ObjectManager::getInstance()->get(CategoryRepositoryInterface::class); + $this->config = $config ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -173,9 +183,13 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ $mergeDataProvider->merge( $this->canonicalUrlRewriteGenerator->generate($storeId, $product) ); - $mergeDataProvider->merge( - $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories) - ); + + if ($this->isCategoryRewritesEnabled()) { + $mergeDataProvider->merge( + $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories) + ); + } + $mergeDataProvider->merge( $this->currentUrlRewritesRegenerator->generate( $storeId, @@ -184,9 +198,13 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ $rootCategoryId ) ); - $mergeDataProvider->merge( - $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories) - ); + + if ($this->isCategoryRewritesEnabled()) { + $mergeDataProvider->merge( + $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories) + ); + } + $mergeDataProvider->merge( $this->currentUrlRewritesRegenerator->generateAnchor( $storeId, @@ -195,6 +213,7 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ $rootCategoryId ) ); + return $mergeDataProvider->getData(); } @@ -216,10 +235,12 @@ public function isCategoryProperForGenerating(Category $category, $storeId) } /** + * Check if URL key has been changed + * * Checks if URL key has been changed for provided category and returns reloaded category, * in other case - returns provided category. * - * @param $storeId + * @param int $storeId * @param Category $category * @return Category */ @@ -236,4 +257,14 @@ private function getCategoryWithOverriddenUrlKey($storeId, Category $category) } return $this->categoryRepository->get($category->getEntityId(), $storeId); } + + /** + * Check config value of generate_category_product_rewrites + * + * @return bool + */ + private function isCategoryRewritesEnabled() + { + return (bool)$this->config->getValue('catalog/seo/generate_category_product_rewrites'); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index 4fdb9a3e2138d..ac3a5092bb3bf 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -3,8 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogUrlRewrite\Model; +use Magento\Store\Model\Store; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class ProductUrlPathGenerator + */ class ProductUrlPathGenerator { const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; @@ -17,36 +30,36 @@ class ProductUrlPathGenerator protected $productUrlSuffix = []; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $scopeConfig; /** - * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator + * @var CategoryUrlPathGenerator */ protected $categoryUrlPathGenerator; /** - * @var \Magento\Catalog\Api\ProductRepositoryInterface + * @var ProductRepositoryInterface */ protected $productRepository; /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig * @param CategoryUrlPathGenerator $categoryUrlPathGenerator - * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + * @param ProductRepositoryInterface $productRepository */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig, + CategoryUrlPathGenerator $categoryUrlPathGenerator, + ProductRepositoryInterface $productRepository ) { $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; @@ -57,8 +70,8 @@ public function __construct( /** * Retrieve Product Url path (with category if exists) * - * @param \Magento\Catalog\Model\Product $product - * @param \Magento\Catalog\Model\Category $category + * @param Product $product + * @param Category $category * * @return string */ @@ -78,10 +91,10 @@ public function getUrlPath($product, $category = null) /** * Prepare URL Key with stored product data (fallback for "Use Default Value" logic) * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return string */ - protected function prepareProductDefaultUrlKey(\Magento\Catalog\Model\Product $product) + protected function prepareProductDefaultUrlKey(Product $product) { $storedProduct = $this->productRepository->getById($product->getId()); $storedUrlKey = $storedProduct->getUrlKey(); @@ -91,9 +104,9 @@ protected function prepareProductDefaultUrlKey(\Magento\Catalog\Model\Product $p /** * Retrieve Product Url path with suffix * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @param int $storeId - * @param \Magento\Catalog\Model\Category $category + * @param Category $category * @return string */ public function getUrlPathWithSuffix($product, $storeId, $category = null) @@ -104,8 +117,8 @@ public function getUrlPathWithSuffix($product, $storeId, $category = null) /** * Get canonical product url path * - * @param \Magento\Catalog\Model\Product $product - * @param \Magento\Catalog\Model\Category|null $category + * @param Product $product + * @param Category|null $category * @return string */ public function getCanonicalUrlPath($product, $category = null) @@ -117,7 +130,7 @@ public function getCanonicalUrlPath($product, $category = null) /** * Generate product url key based on url_key entered by merchant or product name * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return string|null */ public function getUrlKey($product) @@ -129,13 +142,15 @@ public function getUrlKey($product) /** * Prepare url key for product * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return string */ - protected function prepareProductUrlKey(\Magento\Catalog\Model\Product $product) + protected function prepareProductUrlKey(Product $product) { - $urlKey = $product->getUrlKey(); - return $product->formatUrlKey($urlKey === '' || $urlKey === null ? $product->getName() : $urlKey); + $urlKey = (string)$product->getUrlKey(); + $urlKey = trim(strtolower($urlKey)); + + return $urlKey ?: $product->formatUrlKey($product->getName()); } /** @@ -153,7 +168,7 @@ protected function getProductUrlSuffix($storeId = null) if (!isset($this->productUrlSuffix[$storeId])) { $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( self::XML_PATH_PRODUCT_URL_SUFFIX, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId ); } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php index 311cc6de76114..a475e3d5f4b82 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Model\ResourceModel\Category; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; @@ -49,7 +52,7 @@ protected function _construct() public function saveMultiple(array $insertData) { $connection = $this->getConnection(); - if (sizeof($insertData) <= self::CHUNK_SIZE) { + if (count($insertData) <= self::CHUNK_SIZE) { return $connection->insertMultiple($this->getTable(self::TABLE_NAME), $insertData); } $data = array_chunk($insertData, self::CHUNK_SIZE); @@ -98,10 +101,13 @@ public function removeMultipleByProductCategory(array $filter) private function prepareSelect($data) { $select = $this->getConnection()->select(); - $select->from($this->getTable(DbStorage::TABLE_NAME), 'url_rewrite_id'); - + $select->from(DbStorage::TABLE_NAME); + $select->join( + self::TABLE_NAME, + DbStorage::TABLE_NAME . '.url_rewrite_id = ' . self::TABLE_NAME . '.url_rewrite_id' + ); foreach ($data as $column => $value) { - $select->where($this->getConnection()->quoteIdentifier($column) . ' IN (?)', $value); + $select->where(DbStorage::TABLE_NAME . '.' . $column . ' IN (?)', $value); } return $select; } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php index f0351467e5f0e..4bbecbfae6bcf 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DbStorage.php @@ -3,32 +3,40 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Model\Storage; use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product; use Magento\UrlRewrite\Model\Storage\DbStorage as BaseDbStorage; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +/** + * Class DbStorage + */ class DbStorage extends BaseDbStorage { /** - * {@inheritDoc} + * @inheritDoc */ protected function prepareSelect(array $data) { $metadata = []; - if (array_key_exists(UrlRewrite::METADATA, $data)) { + if (isset($data[UrlRewrite::METADATA])) { $metadata = $data[UrlRewrite::METADATA]; unset($data[UrlRewrite::METADATA]); } $select = $this->connection->select(); - $select->from([ - 'url_rewrite' => $this->resource->getTableName(self::TABLE_NAME) - ]); + $select->from( + [ + 'url_rewrite' => $this->resource->getTableName(self::TABLE_NAME) + ] + ); $select->joinLeft( ['relation' => $this->resource->getTableName(Product::TABLE_NAME)], - 'url_rewrite.url_rewrite_id = relation.url_rewrite_id' + 'url_rewrite.url_rewrite_id = relation.url_rewrite_id', + ['relation.category_id', 'relation.product_id'] ); foreach ($data as $column => $value) { diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Storage/DynamicStorage.php b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DynamicStorage.php new file mode 100644 index 0000000000000..edca633fb14cc --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Storage/DynamicStorage.php @@ -0,0 +1,259 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Model\Storage; + +use Magento\Catalog\Model\ResourceModel\ProductFactory; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product; +use Magento\Store\Model\ScopeInterface; +use Magento\UrlRewrite\Model\OptionProvider; +use Magento\UrlRewrite\Model\Storage\DbStorage as BaseDbStorage; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use Psr\Log\LoggerInterface; + +/** + * Class DbStorage + */ +class DynamicStorage extends BaseDbStorage +{ + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var ProductFactory + */ + private $productFactory; + + /** + * @param UrlRewriteFactory $urlRewriteFactory + * @param DataObjectHelper $dataObjectHelper + * @param ResourceConnection $resource + * @param ScopeConfigInterface $config + * @param ProductFactory $productFactory + * @param LoggerInterface|null $logger + */ + public function __construct( + UrlRewriteFactory $urlRewriteFactory, + DataObjectHelper $dataObjectHelper, + ResourceConnection $resource, + ScopeConfigInterface $config, + ProductFactory $productFactory, + LoggerInterface $logger = null + ) { + parent::__construct($urlRewriteFactory, $dataObjectHelper, $resource, $logger); + $this->config = $config; + $this->productFactory = $productFactory; + } + + /** + * @inheritDoc + */ + protected function prepareSelect(array $data) + { + $metadata = []; + if (isset($data[UrlRewrite::METADATA])) { + $metadata = $data[UrlRewrite::METADATA]; + unset($data[UrlRewrite::METADATA]); + } + $select = $this->connection->select(); + $select->from( + [ + 'url_rewrite' => $this->resource->getTableName(self::TABLE_NAME) + ] + ); + $select->joinLeft( + ['relation' => $this->resource->getTableName(Product::TABLE_NAME)], + 'url_rewrite.url_rewrite_id = relation.url_rewrite_id' + ); + foreach ($data as $column => $value) { + $select->where('url_rewrite.' . $column . ' IN (?)', $value); + } + if (empty($metadata['category_id'])) { + $select->where('relation.category_id IS NULL'); + } else { + $select->where( + 'relation.category_id = ?', + $metadata['category_id'] + ); + } + return $select; + } + + /** + * @inheritdoc + */ + protected function doFindOneByData(array $data) + { + if (isset($data[UrlRewrite::REQUEST_PATH]) + && isset($data[UrlRewrite::STORE_ID]) + && is_string($data[UrlRewrite::REQUEST_PATH]) + ) { + return $this->findProductRewriteByRequestPath($data); + } + + $filterResults = $this->findProductRewritesByFilter($data); + if (!empty($filterResults)) { + return reset($filterResults); + } else { + return null; + } + } + + /** + * @inheritdoc + */ + protected function doFindAllByData(array $data) + { + $rewrites = parent::doFindAllByData($data); + + $remainingProducts = []; + if (isset($data[UrlRewrite::ENTITY_ID]) && is_array($data[UrlRewrite::ENTITY_ID])) { + $remainingProducts = array_fill_keys($data[UrlRewrite::ENTITY_ID], 1); + foreach ($rewrites as $rewrite) { + $id = $rewrite[UrlRewrite::ENTITY_ID]; + if (isset($remainingProducts[$id])) { + unset($remainingProducts[$id]); + } + } + } + + if (!empty($remainingProducts)) { + $data[UrlRewrite::ENTITY_ID] = array_keys($remainingProducts); + $rewrites = array_merge($rewrites, $this->findProductRewritesByFilter($data)); + } + + return $rewrites; + } + + /** + * Get category urlSuffix from config + * + * @param int $storeId + * @return string + */ + private function getCategoryUrlSuffix($storeId = null): string + { + return $this->config->getValue( + CategoryUrlPathGenerator::XML_PATH_CATEGORY_URL_SUFFIX, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * Find product rewrite by request data + * + * @param array $data + * @return array|null + */ + private function findProductRewriteByRequestPath(array $data): ?array + { + $requestPath = $data[UrlRewrite::REQUEST_PATH] ?? null; + + $productUrl = $this->getBaseName($requestPath); + $data[UrlRewrite::REQUEST_PATH] = [$productUrl]; + + $productFromDb = $this->connection->fetchRow($this->prepareSelect($data)); + if ($productFromDb === false) { + return null; + } + $categorySuffix = $this->getCategoryUrlSuffix($data[UrlRewrite::STORE_ID]); + $productResource = $this->productFactory->create(); + $categoryPath = str_replace('/' . $productUrl, '', $requestPath); + if ($productFromDb[UrlRewrite::REDIRECT_TYPE]) { + $productUrl = $productFromDb[UrlRewrite::TARGET_PATH]; + } + if ($categoryPath) { + $data[UrlRewrite::REQUEST_PATH] = [$categoryPath . $categorySuffix]; + unset($data[UrlRewrite::IS_AUTOGENERATED]); + $categoryFromDb = $this->connection->fetchRow($this->prepareSelect($data)); + + if ($categoryFromDb[UrlRewrite::REDIRECT_TYPE]) { + $productFromDb[UrlRewrite::REDIRECT_TYPE] = OptionProvider::PERMANENT; + $categoryPath = str_replace($categorySuffix, '', $categoryFromDb[UrlRewrite::TARGET_PATH]); + } + + if ($categoryFromDb === false + || !$productResource->canBeShowInCategory( + $productFromDb[UrlRewrite::ENTITY_ID], + $categoryFromDb[UrlRewrite::ENTITY_ID] + ) + ) { + return null; + } + + $productFromDb[UrlRewrite::TARGET_PATH] = $productFromDb[UrlRewrite::TARGET_PATH] + . '/category/' . $categoryFromDb[UrlRewrite::ENTITY_ID]; + } + + if ($productFromDb[UrlRewrite::REDIRECT_TYPE]) { + $productFromDb[UrlRewrite::TARGET_PATH] = $categoryPath . '/' . $productUrl; + } + + $productFromDb[UrlRewrite::REQUEST_PATH] = $requestPath; + + return $productFromDb; + } + + /** + * Find product rewrites by filter array + * + * @param array $data + * @return array + */ + private function findProductRewritesByFilter(array $data): array + { + if (empty($data[UrlRewrite::ENTITY_TYPE])) { + return []; + } + $rewrites = []; + $metadata = $data[UrlRewrite::METADATA] ?? []; + if (isset($data[UrlRewrite::METADATA])) { + unset($data[UrlRewrite::METADATA]); + } + $productsFromDb = $this->connection->fetchAll($this->prepareSelect($data)); + + if (!empty($metadata['category_id'])) { + $categoryId = $metadata['category_id']; + $data[UrlRewrite::ENTITY_ID] = $categoryId; + $data[UrlRewrite::ENTITY_TYPE] = 'category'; + $categoryFromDb = $this->connection->fetchRow($this->prepareSelect($data)); + foreach ($productsFromDb as $productFromDb) { + $productUrl = $this->getBaseName($productFromDb[UrlRewrite::REQUEST_PATH]); + $productFromDb[UrlRewrite::REQUEST_PATH] = str_replace( + $this->getCategoryUrlSuffix($data[UrlRewrite::STORE_ID]), + '', + $categoryFromDb[UrlRewrite::REQUEST_PATH] + ) + . '/' . $productUrl; + $rewrites[] = $productFromDb; + } + } else { + $rewrites = $productsFromDb; + } + + return $rewrites; + } + + /** + * Return base name for path + * + * @param string|null $string + * @return string + */ + private function getBaseName($string): string + { + return preg_replace('|.*?([^/]+)$|', '\1', $string, 1); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/TableCleaner.php b/app/code/Magento/CatalogUrlRewrite/Model/TableCleaner.php new file mode 100644 index 0000000000000..070836380ac63 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/TableCleaner.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Model; + +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value as ConfigValue; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\Context; +use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\Registry; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewrite; + +/** + * Table Cleaner in case of switching generate_category_product_rewrites off + */ +class TableCleaner extends ConfigValue +{ + const AUTO_GENERATED_ROW_FLAG = 1; + const URL_REWRITE_GENERATION_OFF_FLAG = 0; + + /** + * @var UrlRewrite + */ + private $urlRewrite; + + /** + * @param UrlRewrite $urlRewrite + * @param Context $context + * @param Registry $registry + * @param ScopeConfigInterface $config + * @param TypeListInterface $cacheTypeList + * @param AbstractResource|null $resource + * @param AbstractDb|null $resourceCollection + * @param array $data + */ + public function __construct( + UrlRewrite $urlRewrite, + Context $context, + Registry $registry, + ScopeConfigInterface $config, + TypeListInterface $cacheTypeList, + AbstractResource $resource = null, + AbstractDb $resourceCollection = null, + array $data = [] + ) { + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + $this->urlRewrite = $urlRewrite; + } + + /** + * @inheritDoc + * @return ConfigValue + * @throws LocalizedException + */ + public function afterSave() + { + if ($this->getValue() == self::URL_REWRITE_GENERATION_OFF_FLAG) { + $this->clearOldData(); + } + return parent::afterSave(); + } + + /** + * Clear urlrewrites for products in categories + */ + private function clearOldData(): void + { + $tableName = $this->urlRewrite->getMainTable(); + $conditions = [ + 'metadata LIKE ?' => '{"category_id"%', + 'is_autogenerated = ?' => self::AUTO_GENERATED_ROW_FLAG + ]; + $this->urlRewrite->getConnection()->delete($tableName, $conditions); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 7b60c85049767..33c0cafc8f081 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -6,22 +6,36 @@ namespace Magento\CatalogUrlRewrite\Observer; use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; +use Magento\CatalogUrlRewrite\Model\ObjectRegistry; +use Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\ImportExport\Model\Import as ImportExport; use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException; +use Magento\UrlRewrite\Model\MergeDataProvider; use Magento\UrlRewrite\Model\MergeDataProviderFactory; use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use RuntimeException; /** * Class AfterImportDataObserver @@ -37,12 +51,12 @@ class AfterImportDataObserver implements ObserverInterface const URL_KEY_ATTRIBUTE_CODE = 'url_key'; /** - * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService + * @var StoreViewService */ protected $storeViewService; /** - * @var \Magento\Catalog\Model\Product + * @var Product */ protected $product; @@ -57,42 +71,42 @@ class AfterImportDataObserver implements ObserverInterface protected $products = []; /** - * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory + * @var ObjectRegistryFactory */ protected $objectRegistryFactory; /** - * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry + * @var ObjectRegistry */ protected $productCategories; /** - * @var \Magento\UrlRewrite\Model\UrlFinderInterface + * @var UrlFinderInterface */ protected $urlFinder; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** - * @var \Magento\UrlRewrite\Model\UrlPersistInterface + * @var UrlPersistInterface */ protected $urlPersist; /** - * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory + * @var UrlRewriteFactory */ protected $urlRewriteFactory; /** - * @var \Magento\CatalogImportExport\Model\Import\Product + * @var ImportProduct */ protected $import; /** - * @var \Magento\Catalog\Model\ProductFactory + * @var ProductFactory */ protected $catalogProductFactory; @@ -102,7 +116,7 @@ class AfterImportDataObserver implements ObserverInterface protected $acceptableCategories; /** - * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator + * @var ProductUrlPathGenerator */ protected $productUrlPathGenerator; @@ -139,7 +153,7 @@ class AfterImportDataObserver implements ObserverInterface ]; /** - * @var \Magento\UrlRewrite\Model\MergeDataProvider + * @var MergeDataProvider */ private $mergeDataProviderPrototype; @@ -158,29 +172,37 @@ class AfterImportDataObserver implements ObserverInterface private $categoriesCache = []; /** - * @param \Magento\Catalog\Model\ProductFactory $catalogProductFactory - * @param \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory - * @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator - * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @var ScopeConfigInterface|null + */ + private $scopeConfig; + + /** + * @param ProductFactory $catalogProductFactory + * @param ObjectRegistryFactory $objectRegistryFactory + * @param ProductUrlPathGenerator $productUrlPathGenerator + * @param StoreViewService $storeViewService + * @param StoreManagerInterface $storeManager * @param UrlPersistInterface $urlPersist * @param UrlRewriteFactory $urlRewriteFactory * @param UrlFinderInterface $urlFinder - * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory + * @param MergeDataProviderFactory|null $mergeDataProviderFactory * @param CategoryCollectionFactory|null $categoryCollectionFactory + * @param ScopeConfigInterface|null $scopeConfig + * @throws RuntimeException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Catalog\Model\ProductFactory $catalogProductFactory, - \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory, - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, - \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService, - \Magento\Store\Model\StoreManagerInterface $storeManager, + ProductFactory $catalogProductFactory, + ObjectRegistryFactory $objectRegistryFactory, + ProductUrlPathGenerator $productUrlPathGenerator, + StoreViewService $storeViewService, + StoreManagerInterface $storeManager, UrlPersistInterface $urlPersist, UrlRewriteFactory $urlRewriteFactory, UrlFinderInterface $urlFinder, MergeDataProviderFactory $mergeDataProviderFactory = null, - CategoryCollectionFactory $categoryCollectionFactory = null + CategoryCollectionFactory $categoryCollectionFactory = null, + ScopeConfigInterface $scopeConfig = null ) { $this->urlPersist = $urlPersist; $this->catalogProductFactory = $catalogProductFactory; @@ -196,16 +218,17 @@ public function __construct( $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); $this->categoryCollectionFactory = $categoryCollectionFactory ?: ObjectManager::getInstance()->get(CategoryCollectionFactory::class); + $this->scopeConfig = $scopeConfig ?: + ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** - * Action after data import. - * - * Save new url rewrites and remove old if exist. + * Action after data import. Save new url rewrites and remove old if exist. * * @param Observer $observer - * * @return void + * @throws LocalizedException + * @throws UrlAlreadyExistsException */ public function execute(Observer $observer) { @@ -225,18 +248,15 @@ public function execute(Observer $observer) * Create product model from imported data for URL rewrite purposes. * * @param array $rowData - * - * @return ImportExport + * @return AfterImportDataObserver|null + * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _populateForUrlGeneration($rowData) { $newSku = $this->import->getNewSku($rowData[ImportProduct::COL_SKU]); - if (empty($newSku) || !isset($newSku['entity_id'])) { - return null; - } - if ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE - && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE])) { + $oldSku = $this->import->getOldSku(); + if (!$this->isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku)) { return null; } $rowData['entity_id'] = $newSku['entity_id']; @@ -269,20 +289,41 @@ protected function _populateForUrlGeneration($rowData) return $this; } + /** + * Check is need to populate data for url generation + * + * @param array $rowData + * @param array $newSku + * @param array $oldSku + * @return bool + */ + private function isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku): bool + { + if ((empty($newSku) || !isset($newSku['entity_id'])) + || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE + && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE])) + || (array_key_exists($rowData[ImportProduct::COL_SKU], $oldSku) + && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE]) + && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)) { + return false; + } + return true; + } + /** * Add store id to product data. * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @param array $rowData * @return void */ - protected function setStoreToProduct(\Magento\Catalog\Model\Product $product, array $rowData) + protected function setStoreToProduct(Product $product, array $rowData) { if (!empty($rowData[ImportProduct::COL_STORE]) && ($storeId = $this->import->getStoreIdByCode($rowData[ImportProduct::COL_STORE])) ) { $product->setStoreId($storeId); - } elseif (!$product->hasData(\Magento\Catalog\Model\Product::STORE_ID)) { + } elseif (!$product->hasData(Product::STORE_ID)) { $product->setStoreId(Store::DEFAULT_STORE_ID); } } @@ -290,7 +331,7 @@ protected function setStoreToProduct(\Magento\Catalog\Model\Product $product, ar /** * Add product to import * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @param string $storeId * @return $this */ @@ -309,7 +350,7 @@ protected function addProductToImport($product, $storeId) /** * Populate global product * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return $this */ protected function populateGlobalProduct($product) @@ -328,13 +369,16 @@ protected function populateGlobalProduct($product) /** * Generate product url rewrites * - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] + * @return UrlRewrite[] + * @throws LocalizedException */ protected function generateUrls() { $mergeDataProvider = clone $this->mergeDataProviderPrototype; $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate()); - $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate()); + if ($this->isCategoryRewritesEnabled()) { + $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate()); + } $mergeDataProvider->merge($this->currentUrlRewritesRegenerate()); $this->productCategories = null; @@ -383,6 +427,7 @@ protected function canonicalUrlRewriteGenerate() * Generate list based on categories. * * @return UrlRewrite[] + * @throws LocalizedException */ protected function categoriesUrlRewriteGenerate() { @@ -530,9 +575,10 @@ protected function retrieveCategoryFromMetadata($url) /** * Check, category suited for url-rewrite generation. * - * @param \Magento\Catalog\Model\Category $category + * @param Category $category * @param int $storeId * @return bool + * @throws NoSuchEntityException */ protected function isCategoryProperForGenerating($category, $storeId) { @@ -541,7 +587,7 @@ protected function isCategoryProperForGenerating($category, $storeId) return $this->acceptableCategories[$storeId][$category->getId()]; } $acceptable = false; - if ($category->getParentId() != \Magento\Catalog\Model\Category::TREE_ROOT_ID) { + if ($category->getParentId() != Category::TREE_ROOT_ID) { list(, $rootCategoryId) = $category->getParentIds(); $acceptable = ($rootCategoryId == $this->storeManager->getStore($storeId)->getRootCategoryId()); } @@ -557,7 +603,8 @@ protected function isCategoryProperForGenerating($category, $storeId) * * @param int $categoryId * @param int $storeId - * @return Category|\Magento\Framework\DataObject + * @return Category|DataObject + * @throws LocalizedException */ private function getCategoryById($categoryId, $storeId) { @@ -574,4 +621,14 @@ private function getCategoryById($categoryId, $storeId) return $this->categoriesCache[$categoryId][$storeId]; } + + /** + * Check config value of generate_category_product_rewrites + * + * @return bool + */ + private function isCategoryRewritesEnabled() + { + return (bool)$this->scopeConfig->getValue('catalog/seo/generate_category_product_rewrites'); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteMovingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteMovingObserver.php index 0afdf774c7eb1..244aaf4d5cdc9 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteMovingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteMovingObserver.php @@ -74,8 +74,8 @@ public function __construct( UrlRewriteBunchReplacer $urlRewriteBunchReplacer, DatabaseMapPool $databaseMapPool, $dataUrlRewriteClassNames = [ - DataCategoryUrlRewriteDatabaseMap::class, - DataProductUrlRewriteDatabaseMap::class + DataCategoryUrlRewriteDatabaseMap::class, + DataProductUrlRewriteDatabaseMap::class ] ) { $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; @@ -88,6 +88,8 @@ public function __construct( } /** + * Execute observer functional + * * @param \Magento\Framework\Event\Observer $observer * @return void */ @@ -105,8 +107,12 @@ public function execute(\Magento\Framework\Event\Observer $observer) $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category, true); $this->urlRewriteHandler->deleteCategoryRewritesForChildren($category); $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult); - $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); - $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + + if ($this->isCategoryRewritesEnabled()) { + $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } + //frees memory for maps that are self-initialized in multiple classes that were called by the generators $this->resetUrlRewritesDataMaps($category); } @@ -124,4 +130,14 @@ private function resetUrlRewritesDataMaps($category) $this->databaseMapPool->resetMap($className, $category->getEntityId()); } } + + /** + * Check config value of generate_category_product_rewrites + * + * @return bool + */ + private function isCategoryRewritesEnabled() + { + return (bool)$this->scopeConfig->getValue('catalog/seo/generate_category_product_rewrites'); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php index 3cfd49b1d210a..62ce54cd67185 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php @@ -12,10 +12,12 @@ use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Event\ObserverInterface; use Magento\Store\Model\ResourceModel\Group\CollectionFactory; use Magento\Store\Model\ResourceModel\Group\Collection as StoreGroupCollection; use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\ScopeInterface; /** * Generates Category Url Rewrites after save and Products Url Rewrites assigned to the category that's being saved @@ -52,11 +54,17 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface */ private $storeGroupFactory; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator * @param UrlRewriteHandler $urlRewriteHandler * @param UrlRewriteBunchReplacer $urlRewriteBunchReplacer * @param DatabaseMapPool $databaseMapPool + * @param ScopeConfigInterface $scopeConfig * @param string[] $dataUrlRewriteClassNames * @param CollectionFactory|null $storeGroupFactory */ @@ -65,6 +73,7 @@ public function __construct( UrlRewriteHandler $urlRewriteHandler, UrlRewriteBunchReplacer $urlRewriteBunchReplacer, DatabaseMapPool $databaseMapPool, + ScopeConfigInterface $scopeConfig, $dataUrlRewriteClassNames = [ DataCategoryUrlRewriteDatabaseMap::class, DataProductUrlRewriteDatabaseMap::class @@ -78,6 +87,7 @@ public function __construct( $this->dataUrlRewriteClassNames = $dataUrlRewriteClassNames; $this->storeGroupFactory = $storeGroupFactory ?: ObjectManager::getInstance()->get(CollectionFactory::class); + $this->scopeConfig = $scopeConfig; } /** @@ -105,13 +115,15 @@ public function execute(\Magento\Framework\Event\Observer $observer) $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category); $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult); } - if ($this->isChangedOnlyProduct($category)) { - $productUrlRewriteResult = - $this->urlRewriteHandler->updateProductUrlRewritesForChangedProduct($category); - $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); - } else { - $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); - $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + if ($this->isCategoryRewritesEnabled()) { + if ($this->isChangedOnlyProduct($category)) { + $productUrlRewriteResult = + $this->urlRewriteHandler->updateProductUrlRewritesForChangedProduct($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } else { + $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } } $mapsGenerated = true; } @@ -189,4 +201,14 @@ private function resetUrlRewritesDataMaps($category) $this->databaseMapPool->resetMap($className, $category->getEntityId()); } } + + /** + * Check config value of generate_category_product_rewrites + * + * @return bool + */ + private function isCategoryRewritesEnabled() + { + return (bool)$this->scopeConfig->getValue('catalog/seo/generate_category_product_rewrites'); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index b4a35f323e1bc..083b39d621f2a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -16,6 +16,7 @@ use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Serialize\Serializer\Json; use Magento\UrlRewrite\Model\MergeDataProvider; @@ -80,6 +81,11 @@ class UrlRewriteHandler */ private $productScopeRewriteGenerator; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param ChildrenCategoriesProvider $childrenCategoriesProvider * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator @@ -90,6 +96,8 @@ class UrlRewriteHandler * @param MergeDataProviderFactory|null $mergeDataProviderFactory * @param Json|null $serializer * @param ProductScopeRewriteGenerator|null $productScopeRewriteGenerator + * @param ScopeConfigInterface|null $scopeConfig + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( ChildrenCategoriesProvider $childrenCategoriesProvider, @@ -100,7 +108,8 @@ public function __construct( CategoryProductUrlPathGenerator $categoryBasedProductRewriteGenerator, MergeDataProviderFactory $mergeDataProviderFactory = null, Json $serializer = null, - ProductScopeRewriteGenerator $productScopeRewriteGenerator = null + ProductScopeRewriteGenerator $productScopeRewriteGenerator = null, + ScopeConfigInterface $scopeConfig = null ) { $this->childrenCategoriesProvider = $childrenCategoriesProvider; $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; @@ -115,6 +124,7 @@ public function __construct( $this->serializer = $serializer ?: $objectManager->get(Json::class); $this->productScopeRewriteGenerator = $productScopeRewriteGenerator ?: $objectManager->get(ProductScopeRewriteGenerator::class); + $this->scopeConfig = $scopeConfig ?? $objectManager->get(ScopeConfigInterface::class); } /** @@ -133,14 +143,19 @@ public function generateProductUrlRewrites(Category $category): array if ($category->getChangedProductIds()) { $this->generateChangedProductUrls($mergeDataProvider, $category, $storeId, $saveRewriteHistory); } else { - $mergeDataProvider->merge( - $this->getCategoryProductsUrlRewrites( - $category, - $storeId, - $saveRewriteHistory, - $category->getEntityId() - ) - ); + $categoryStoreIds = $this->getCategoryStoreIds($category); + + foreach ($categoryStoreIds as $categoryStoreId) { + $this->isSkippedProduct[$category->getEntityId()] = []; + $mergeDataProvider->merge( + $this->getCategoryProductsUrlRewrites( + $category, + $categoryStoreId, + $saveRewriteHistory, + $category->getEntityId() + ) + ); + } } foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) { @@ -225,12 +240,13 @@ private function getCategoryProductsUrlRewrites( $rootCategoryId = null ) { $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $generateProductRewrite = (bool)$this->scopeConfig->getValue('catalog/seo/generate_category_product_rewrites'); /** @var Collection $productCollection */ $productCollection = $this->productCollectionFactory->create(); $productCollection->addCategoriesFilter(['eq' => [$category->getEntityId()]]) - ->setStoreId($storeId) + ->addStoreFilter($storeId) ->addAttributeToSelect('name') ->addAttributeToSelect('visibility') ->addAttributeToSelect('url_key') @@ -245,6 +261,7 @@ private function getCategoryProductsUrlRewrites( $this->isSkippedProduct[$category->getEntityId()][] = $product->getId(); $product->setStoreId($storeId); $product->setData('save_rewrites_history', $saveRewriteHistory); + $product->setData('generate_rewrites', $generateProductRewrite); $mergeDataProvider->merge( $this->categoryBasedProductRewriteGenerator->generate($product, $rootCategoryId) ); @@ -280,7 +297,7 @@ private function generateChangedProductUrls( /* @var Collection $collection */ $collection = $this->productCollectionFactory->create() ->setStoreId($categoryStoreId) - ->addIdFilter($category->getAffectedProductIds()) + ->addIdFilter($category->getChangedProductIds()) ->addAttributeToSelect('visibility') ->addAttributeToSelect('name') ->addAttributeToSelect('url_key') diff --git a/app/code/Magento/CatalogUrlRewrite/Plugin/DynamicCategoryRewrites.php b/app/code/Magento/CatalogUrlRewrite/Plugin/DynamicCategoryRewrites.php new file mode 100644 index 0000000000000..139452116f5d2 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Plugin/DynamicCategoryRewrites.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Plugin; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\CatalogUrlRewrite\Model\Storage\DynamicStorage; +use Magento\CatalogUrlRewrite\Model\Storage\DbStorage; + +/** + * Class DbStorage + */ +class DynamicCategoryRewrites +{ + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var DynamicStorage + */ + private $dynamicStorage; + + /** + * @param ScopeConfigInterface|null $config + * @param DynamicStorage $dynamicStorage + */ + public function __construct( + ScopeConfigInterface $config, + DynamicStorage $dynamicStorage + ) { + $this->config = $config; + $this->dynamicStorage = $dynamicStorage; + } + + /** + * Check config value of generate_category_product_rewrites + * + * @return bool + */ + private function isCategoryRewritesEnabled(): bool + { + return (bool)$this->config->getValue('catalog/seo/generate_category_product_rewrites'); + } + + /** + * Execute proxy + * + * @param callable $proceed + * @param array $data + * @param string $functionName + * @return mixed + */ + private function proxy(callable $proceed, array $data, string $functionName) + { + if ($this->isCategoryRewritesEnabled()) { + return $proceed($data); + } + + return $this->dynamicStorage->$functionName($data); + } + + /** + * Find rewrite by specific data + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param DbStorage $subject + * @param callable $proceed + * @param array $data + * @return UrlRewrite|null + */ + public function aroundFindOneByData(DbStorage $subject, callable $proceed, array $data) + { + return $this->proxy($proceed, $data, 'findOneByData'); + } + + /** + * Find rewrites by specific data + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param DbStorage $subject + * @param callable $proceed + * @param array $data + * @return UrlRewrite[] + */ + public function aroundFindAllByData(DbStorage $subject, callable $proceed, array $data) + { + return $this->proxy($proceed, $data, 'findAllByData'); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml index 30a4290d882fb..5423267676507 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml @@ -10,6 +10,7 @@ <test name="AdminUrlForProductRewrittenCorrectlyTest"> <annotations> <features value="CatalogUrlRewrite"/> + <stories value="Url rewrites"/> <title value="Check that URL for product rewritten correctly"/> <description value="Check that URL for product rewritten correctly"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php index 06be01445df4c..d4e0978dda66e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php @@ -50,6 +50,9 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ private $categoryMock; + /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $configMock; + public function setUp() { $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); @@ -96,6 +99,7 @@ function ($value) { ); $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider(); $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); + $this->configMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)->getMock(); $this->productScopeGenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator::class, @@ -107,7 +111,8 @@ function ($value) { 'objectRegistryFactory' => $this->objectRegistryFactory, 'storeViewService' => $this->storeViewService, 'storeManager' => $this->storeManager, - 'mergeDataProviderFactory' => $mergeDataProviderFactory + 'mergeDataProviderFactory' => $mergeDataProviderFactory, + 'config' => $this->configMock ] ); $this->categoryMock = $this->getMockBuilder(Category::class)->disableOriginalConstructor()->getMock(); @@ -115,6 +120,9 @@ function ($value) { public function testGenerationForGlobalScope() { + $this->configMock->expects($this->any())->method('getValue') + ->with('catalog/seo/generate_category_product_rewrites') + ->willReturn('1'); $product = $this->createMock(\Magento\Catalog\Model\Product::class); $product->expects($this->any())->method('getStoreId')->will($this->returnValue(null)); $product->expects($this->any())->method('getStoreIds')->will($this->returnValue([1])); diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index 7435096642de2..5076577447af3 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -11,6 +11,9 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\ScopeInterface; +/** + * Class ProductUrlPathGeneratorTest + */ class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator */ @@ -77,10 +80,11 @@ protected function setUp(): void public function getUrlPathDataProvider(): array { return [ - 'path based on url key' => ['url-key', null, 'url-key'], - 'path based on product name 1' => ['', 'product-name', 'product-name'], - 'path based on product name 2' => [null, 'product-name', 'product-name'], - 'path based on product name 3' => [false, 'product-name', 'product-name'] + 'path based on url key uppercase' => ['Url-Key', null, 0, 'url-key'], + 'path based on url key' => ['url-key', null, 0, 'url-key'], + 'path based on product name 1' => ['', 'product-name', 1, 'product-name'], + 'path based on product name 2' => [null, 'product-name', 1, 'product-name'], + 'path based on product name 3' => [false, 'product-name', 1, 'product-name'] ]; } @@ -88,16 +92,18 @@ public function getUrlPathDataProvider(): array * @dataProvider getUrlPathDataProvider * @param string|null|bool $urlKey * @param string|null|bool $productName + * @param int $formatterCalled * @param string $result * @return void */ - public function testGetUrlPath($urlKey, $productName, $result): void + public function testGetUrlPath($urlKey, $productName, $formatterCalled, $result): void { $this->product->expects($this->once())->method('getData')->with('url_path') ->will($this->returnValue(null)); $this->product->expects($this->any())->method('getUrlKey')->will($this->returnValue($urlKey)); $this->product->expects($this->any())->method('getName')->will($this->returnValue($productName)); - $this->product->expects($this->once())->method('formatUrlKey')->will($this->returnArgument(0)); + $this->product->expects($this->exactly($formatterCalled)) + ->method('formatUrlKey')->will($this->returnArgument(0)); $this->assertEquals($result, $this->productUrlPathGenerator->getUrlPath($this->product, null)); } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php index b12da6243a903..57162b44f9826 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php @@ -98,12 +98,15 @@ public function testCategoryProcessUrlRewriteAfterMovingWithChangedParentId() ->disableOriginalConstructor() ->setMethods(['getCategory']) ->getMock(); - $categoryMock = $this->createPartialMock(Category::class, [ - 'dataHasChangedFor', - 'getEntityId', - 'getStoreId', - 'setData' - ]); + $categoryMock = $this->createPartialMock( + Category::class, + [ + 'dataHasChangedFor', + 'getEntityId', + 'getStoreId', + 'setData' + ] + ); $categoryMock->expects($this->once())->method('dataHasChangedFor')->with('parent_id') ->willReturn(true); @@ -111,6 +114,7 @@ public function testCategoryProcessUrlRewriteAfterMovingWithChangedParentId() $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); $this->scopeConfigMock->expects($this->once())->method('isSetFlag') ->with(UrlKeyRenderer::XML_PATH_SEO_SAVE_HISTORY)->willReturn(true); + $this->scopeConfigMock->method('getValue')->willReturn(true); $this->categoryUrlRewriteGeneratorMock->expects($this->once())->method('generate') ->with($categoryMock, true)->willReturn(['category-url-rewrite']); $this->urlRewriteHandlerMock->expects($this->once())->method('generateProductUrlRewrites') diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteSavingObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteSavingObserverTest.php index afdb548887577..cdea134758101 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteSavingObserverTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteSavingObserverTest.php @@ -12,6 +12,7 @@ use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer; use Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler; use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool; +use Magento\Framework\App\Config\ScopeConfigInterface as ScopeConfigInterfaceAlias; use Magento\Store\Model\ResourceModel\Group\CollectionFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Catalog\Model\Category; @@ -61,6 +62,11 @@ class CategoryProcessUrlRewriteSavingObserverTest extends \PHPUnit\Framework\Tes */ private $storeGroupFactory; + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ + private $scopeConfigMock; + /** * {@inheritDoc} */ @@ -70,12 +76,16 @@ protected function setUp() \Magento\Framework\Event\Observer::class, ['getEvent', 'getData'] ); - $this->category = $this->createPartialMock(Category::class, [ - 'hasData', - 'getParentId', - 'dataHasChangedFor', - 'getChangedProductIds', - ]); + $this->category = $this->createPartialMock( + Category::class, + [ + 'hasData', + 'getParentId', + 'getStoreId', + 'dataHasChangedFor', + 'getChangedProductIds', + ] + ); $this->observer->expects($this->any()) ->method('getEvent') ->willReturnSelf(); @@ -100,6 +110,11 @@ protected function setUp() ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterfaceAlias::class) + ->setMethods(['getValue']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->scopeConfigMock->method('getValue')->willReturn(true); $this->categoryProcessUrlRewriteSavingObserver = (new ObjectManagerHelper($this))->getObject( CategoryProcessUrlRewriteSavingObserver::class, @@ -109,6 +124,7 @@ protected function setUp() 'urlRewriteBunchReplacer' => $this->urlRewriteBunchReplacerMock, 'databaseMapPool' => $this->databaseMapPoolMock, 'storeGroupFactory' => $this->storeGroupFactory, + 'scopeConfig' => $this->scopeConfigMock ] ); } @@ -200,6 +216,7 @@ public function testExecuteHasChanges() $this->category->expects($this->any()) ->method('getChangedProductIds') ->willReturn([]); + $this->category->method('getStoreId')->willReturn(1); $result1 = ['test']; $this->categoryUrlRewriteGeneratorMock->expects($this->once()) diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php index b18597a42bf94..06a89a9dd5ec0 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php @@ -138,8 +138,14 @@ public function testGenerateProductUrlRewrites() ->willReturn(1); $category->expects($this->any()) ->method('getData') - ->with('save_rewrites_history') - ->willReturn(true); + ->withConsecutive( + [$this->equalTo('save_rewrites_history')], + [$this->equalTo('initial_setup_flag')] + ) + ->willReturnOnConsecutiveCalls( + true, + null + ); /* @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject $childCategory1 */ $childCategory1 = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) @@ -175,6 +181,7 @@ public function testGenerateProductUrlRewrites() ->method('addIdFilter') ->willReturnSelf(); $productCollection->expects($this->any())->method('setStoreId')->willReturnSelf(); + $productCollection->expects($this->any())->method('addStoreFilter')->willReturnSelf(); $productCollection->expects($this->any())->method('addAttributeToSelect')->willReturnSelf(); $iterator = new \ArrayIterator([]); $productCollection->expects($this->any())->method('getIterator')->will($this->returnValue($iterator)); diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index de344c505798e..fedcf144b8c0d 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -32,5 +32,5 @@ "Magento\\CatalogUrlRewrite\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml b/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml index 4aa2e7f40c7c0..1e1f4e86fa3dc 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml @@ -28,6 +28,15 @@ <label>Create Permanent Redirect for URLs if URL Key Changed</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> + <field id="generate_category_product_rewrites" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Generate "category/product" URL Rewrites</label> + <backend_model>Magento\CatalogUrlRewrite\Model\TableCleaner</backend_model> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment> + <![CDATA[<strong style="color:red">Warning!</strong> Turning this option off will result in permanent removal of category/product URL rewrites without an ability to restore them.]]> + </comment> + <frontend_class>generate_category_product_rewrites</frontend_class> + </field> </group> </section> </system> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/config.xml b/app/code/Magento/CatalogUrlRewrite/etc/config.xml new file mode 100644 index 0000000000000..d05c0b4b7aa59 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/etc/config.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <catalog> + <seo> + <generate_category_product_rewrites>1</generate_category_product_rewrites> + </seo> + </catalog> + </default> +</config> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/di.xml index f6426677e8ce8..e6fbcaefd0768 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/di.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/di.xml @@ -24,6 +24,9 @@ <type name="Magento\UrlRewrite\Model\StorageInterface"> <plugin name="storage_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage"/> </type> + <type name="Magento\CatalogUrlRewrite\Model\Storage\DbStorage"> + <plugin name="dynamic_storage_plugin" type="Magento\CatalogUrlRewrite\Plugin\DynamicCategoryRewrites"/> + </type> <type name="Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder"> <arguments> <argument name="urlRewriteClassNames" xsi:type="array"> @@ -32,4 +35,14 @@ </argument> </arguments> </type> + <type name="Magento\UrlRewrite\Model\CompositeUrlFinder"> + <arguments> + <argument name="children" xsi:type="array"> + <item name="catalog" xsi:type="array"> + <item name="class" xsi:type="string">Magento\CatalogUrlRewrite\Model\Storage\DbStorage</item> + <item name="sortOrder" xsi:type="number">20</item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml new file mode 100644 index 0000000000000..0c7ab27e51d8c --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + +</config> diff --git a/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv b/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv index 2dce6b233cb95..b3335dc3523ca 100644 --- a/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv +++ b/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv @@ -5,3 +5,4 @@ "Product URL Suffix","Product URL Suffix" "Use Categories Path for Product URLs","Use Categories Path for Product URLs" "Create Permanent Redirect for URLs if URL Key Changed","Create Permanent Redirect for URLs if URL Key Changed" +"Generate "category/product" URL Rewrites","Generate "category/product" URL Rewrites" \ No newline at end of file diff --git a/app/code/Magento/CatalogUrlRewrite/view/adminhtml/layout/adminhtml_system_config_edit.xml b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/layout/adminhtml_system_config_edit.xml new file mode 100644 index 0000000000000..c4766138843a1 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/layout/adminhtml_system_config_edit.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="js"> + <block name="js.confirm_remove_old_urls" template="Magento_CatalogUrlRewrite::confirm.phtml"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml new file mode 100644 index 0000000000000..800ecfd8a6e2f --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/view/adminhtml/templates/confirm.phtml @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + "jquery", + "Magento_Ui/js/modal/confirm", + "mage/translate", + ], function(jQuery, confirmation, $t) { + //confirmation for removing category/product URL rewrites + jQuery('select.generate_category_product_rewrites').on('change', function () { + if (this.value == 0) { + confirmation({ + title: $t('Turn off "category/products" URL rewrites?'), + content: $t('Turning off automatic generation of "category/products" URL rewrites will result in permanent removal of all the currently existing “category/product” type URL rewrites without an ability to restore them back. ' + + 'This may potentially cause unresolved “category/product” type URL conflicts which you have to resolve by updating URL key manually.'), + actions: { + cancel: function () { + jQuery('select.generate_category_product_rewrites').val(1); + return false; + }, + } + }) + } + }); + }); +</script> diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json index 1c18ee8e1a5f8..8a59be452dcc3 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-catalog": "103.0.*", "magento/framework": "102.0.*" }, @@ -24,5 +24,5 @@ "Magento\\CatalogUrlRewriteGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index f4ad2e930ddab..89108e578d673 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -3,18 +3,18 @@ interface ProductInterface { url_key: String @doc(description: "The part of the URL that identifies the product") - url_path: String @doc(description: "The part of the URL that precedes the url_key") + url_path: String @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") url_rewrites: [UrlRewrite] @doc(description: "URL rewrites list") @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") } input ProductFilterInput { url_key: FilterTypeInput @doc(description: "The part of the URL that identifies the product") - url_path: FilterTypeInput @doc(description: "The part of the URL that precedes the url_key") + url_path: FilterTypeInput @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") } input ProductSortInput { url_key: SortEnum @doc(description: "The part of the URL that identifies the product") - url_path: SortEnum @doc(description: "The part of the URL that precedes the url_key") + url_path: SortEnum @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") } enum UrlRewriteEntityTypeEnum @doc(description: "This enumeration defines the entity type.") { diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 070f6212ab991..5c99520deb28e 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -29,5 +29,5 @@ "Magento\\CatalogWidget\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CatalogWidget/view/adminhtml/templates/product/widget/conditions.phtml b/app/code/Magento/CatalogWidget/view/adminhtml/templates/product/widget/conditions.phtml index d40cc3dc6d9ba..0e21f9e42c995 100644 --- a/app/code/Magento/CatalogWidget/view/adminhtml/templates/product/widget/conditions.phtml +++ b/app/code/Magento/CatalogWidget/view/adminhtml/templates/product/widget/conditions.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\CatalogWidget\Block\Product\Widget\Conditions $block */ $element = $block->getElement(); diff --git a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml index 29efe8a8c1c6a..881d8b28dfaeb 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml +++ b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml @@ -5,11 +5,9 @@ */ use Magento\Framework\App\Action\Action; -// @codingStandardsIgnoreFile - /** @var \Magento\CatalogWidget\Block\Product\ProductsList $block */ ?> -<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())): ?> +<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())) : ?> <?php $type = 'widget-product-grid'; @@ -25,7 +23,7 @@ use Magento\Framework\App\Action\Action; $description = false; ?> <div class="block widget block-products-list <?= /* @noEscape */ $mode ?>"> - <?php if ($block->getTitle()): ?> + <?php if ($block->getTitle()) : ?> <div class="block-title"> <strong><?= $block->escapeHtml(__($block->getTitle())) ?></strong> </div> @@ -35,7 +33,7 @@ use Magento\Framework\App\Action\Action; <div class="products-<?= /* @noEscape */ $mode ?> <?= /* @noEscape */ $mode ?>"> <ol class="product-items <?= /* @noEscape */ $type ?>"> <?php $iterator = 1; ?> - <?php foreach ($items as $_item): ?> + <?php foreach ($items as $_item) : ?> <?= /* @noEscape */ ($iterator++ == 1) ? '<li class="product-item">' : '</li><li class="product-item">' ?> <div class="product-item-info"> <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-photo"> @@ -49,7 +47,7 @@ use Magento\Framework\App\Action\Action; <?= $block->escapeHtml($_item->getName()) ?> </a> </strong> - <?php if ($templateType): ?> + <?php if ($templateType) : ?> <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> <?php endif; ?> @@ -57,42 +55,42 @@ use Magento\Framework\App\Action\Action; <?= $block->getProductDetailsHtml($_item) ?> - <?php if ($showWishlist || $showCompare || $showCart): ?> + <?php if ($showWishlist || $showCompare || $showCart) : ?> <div class="product-item-inner"> <div class="product-item-actions"> - <?php if ($showCart): ?> + <?php if ($showCart) : ?> <div class="actions-primary"> - <?php if ($_item->isSaleable()): ?> + <?php if ($_item->isSaleable()) : ?> <?php $postParams = $block->getAddToCartPostParams($_item); ?> - <form data-role="tocart-form" data-product-sku="<?= $block->escapeHtml($_item->getSku()) ?>" action="<?= /* @NoEscape */ $postParams['action'] ?>" method="post"> - <input type="hidden" name="product" value="<?= /* @escapeNotVerified */ $postParams['data']['product'] ?>"> - <input type="hidden" name="<?= /* @escapeNotVerified */ Action::PARAM_NAME_URL_ENCODED ?>" value="<?= /* @escapeNotVerified */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED] ?>"> + <form data-role="tocart-form" data-product-sku="<?= $block->escapeHtml($_item->getSku()) ?>" action="<?= $block->escapeUrl($postParams['action']) ?>" method="post"> + <input type="hidden" name="product" value="<?= $block->escapeHtmlAttr($postParams['data']['product']) ?>"> + <input type="hidden" name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>" value="<?= /* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED] ?>"> <?= $block->getBlockHtml('formkey') ?> <button type="submit" title="<?= $block->escapeHtml(__('Add to Cart')) ?>" class="action tocart primary"> - <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> </button> </form> - <?php else: ?> - <?php if ($_item->getIsSalable()): ?> + <?php else : ?> + <?php if ($_item->getIsSalable()) : ?> <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div> - <?php else: ?> + <?php else : ?> <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div> <?php endif; ?> <?php endif; ?> </div> <?php endif; ?> - <?php if ($showWishlist || $showCompare): ?> + <?php if ($showWishlist || $showCompare) : ?> <div class="actions-secondary" data-role="add-to-links"> - <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow() && $showWishlist): ?> + <?php if ($this->helper(\Magento\Wishlist\Helper\Data::class)->isAllow() && $showWishlist) : ?> <a href="#" data-post='<?= /* @noEscape */ $block->getAddToWishlistParams($_item) ?>' class="action towishlist" data-action="add-to-wishlist" title="<?= $block->escapeHtmlAttr(__('Add to Wish List')) ?>"> <span><?= $block->escapeHtml(__('Add to Wish List')) ?></span> </a> <?php endif; ?> - <?php if ($block->getAddToCompareUrl() && $showCompare): ?> - <?php $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare');?> + <?php if ($block->getAddToCompareUrl() && $showCompare) : ?> + <?php $compareHelper = $this->helper(\Magento\Catalog\Helper\Product\Compare::class);?> <a href="#" class="action tocompare" data-post='<?= /* @noEscape */ $compareHelper->getPostDataParams($_item) ?>' title="<?= $block->escapeHtmlAttr(__('Add to Compare')) ?>"> <span><?= $block->escapeHtml(__('Add to Compare')) ?></span> </a> diff --git a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php index 819030faf1475..830191bd13c40 100644 --- a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php +++ b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php @@ -186,6 +186,8 @@ public function getProductForThumbnail() } /** + * Override product url. + * * @param string $productUrl * @return $this * @codeCoverageIgnore @@ -314,11 +316,7 @@ public function getCheckoutSession() } /** - * Retrieve item messages - * Return array with keys - * - * text => the message text - * type => type of a message + * Retrieve item messages, return array with keys, text => the message text, type => type of a message * * @return array */ @@ -473,6 +471,8 @@ public function getProductPriceHtml(\Magento\Catalog\Model\Product $product) } /** + * Get price renderer. + * * @return \Magento\Framework\Pricing\Render * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Block/Cart/Link.php b/app/code/Magento/Checkout/Block/Cart/Link.php index abc1ce4e31010..9e6db1754d9e4 100644 --- a/app/code/Magento/Checkout/Block/Cart/Link.php +++ b/app/code/Magento/Checkout/Block/Cart/Link.php @@ -41,6 +41,8 @@ public function __construct( } /** + * Get label. + * * @return string * @codeCoverageIgnore */ @@ -50,6 +52,8 @@ public function getLabel() } /** + * Get href. + * * @return string * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Block/Cart/Totals.php b/app/code/Magento/Checkout/Block/Cart/Totals.php index 70e9402b96724..a0ca67f52d73f 100644 --- a/app/code/Magento/Checkout/Block/Cart/Totals.php +++ b/app/code/Magento/Checkout/Block/Cart/Totals.php @@ -8,6 +8,7 @@ use Magento\Framework\View\Element\BlockInterface; use Magento\Checkout\Block\Checkout\LayoutProcessorInterface; +use Magento\Sales\Model\ConfigInterface; /** * Totals cart block. @@ -46,7 +47,7 @@ class Totals extends \Magento\Checkout\Block\Cart\AbstractCart * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Checkout\Model\Session $checkoutSession - * @param \Magento\Sales\Model\Config $salesConfig + * @param ConfigInterface $salesConfig * @param array $layoutProcessors * @param array $data * @codeCoverageIgnore @@ -55,7 +56,7 @@ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Customer\Model\Session $customerSession, \Magento\Checkout\Model\Session $checkoutSession, - \Magento\Sales\Model\Config $salesConfig, + ConfigInterface $salesConfig, array $layoutProcessors = [], array $data = [] ) { diff --git a/app/code/Magento/Checkout/Block/Link.php b/app/code/Magento/Checkout/Block/Link.php index cb2fb3309b83c..4ab2981e9185e 100644 --- a/app/code/Magento/Checkout/Block/Link.php +++ b/app/code/Magento/Checkout/Block/Link.php @@ -41,6 +41,8 @@ public function __construct( } /** + * Get href. + * * @return string * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Controller/Cart.php b/app/code/Magento/Checkout/Controller/Cart.php index d7b09c17ee036..0c65fa9a51f48 100644 --- a/app/code/Magento/Checkout/Controller/Cart.php +++ b/app/code/Magento/Checkout/Controller/Cart.php @@ -98,8 +98,8 @@ protected function _isInternalUrl($url) */ /** @var $store \Magento\Store\Model\Store */ $store = $this->_storeManager->getStore(); - $unsecure = strpos($url, $store->getBaseUrl()) === 0; - $secure = strpos($url, $store->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_LINK, true)) === 0; + $unsecure = strpos($url, (string) $store->getBaseUrl()) === 0; + $secure = strpos($url, (string) $store->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_LINK, true)) === 0; return $unsecure || $secure; } diff --git a/app/code/Magento/Checkout/Controller/Onepage/SaveOrder.php b/app/code/Magento/Checkout/Controller/Onepage/SaveOrder.php index 12b725a8f6df9..66b054136203a 100644 --- a/app/code/Magento/Checkout/Controller/Onepage/SaveOrder.php +++ b/app/code/Magento/Checkout/Controller/Onepage/SaveOrder.php @@ -6,10 +6,14 @@ namespace Magento\Checkout\Controller\Onepage; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\DataObject; use Magento\Framework\Exception\PaymentException; -class SaveOrder extends \Magento\Checkout\Controller\Onepage +/** + * One Page Checkout saveOrder action + */ +class SaveOrder extends \Magento\Checkout\Controller\Onepage implements HttpPostActionInterface { /** * Create order action @@ -31,7 +35,7 @@ public function execute() $result = new DataObject(); try { $agreementsValidator = $this->_objectManager->get( - \Magento\CheckoutAgreements\Model\AgreementsValidator::class + \Magento\Checkout\Api\AgreementsValidatorInterface::class ); if (!$agreementsValidator->isValid(array_keys($this->getRequest()->getPost('agreement', [])))) { $result->setData('success', false); diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index 470d4a3aca561..70352b50d8de4 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -343,6 +343,10 @@ public function getConfig() ) ) ]; + $output['useQty'] = $this->scopeConfig->isSetFlag( + 'checkout/cart_link/use_qty', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); $output['activeCarriers'] = $this->getActiveCarriers(); $output['originCountryCode'] = $this->getOriginCountryCode(); $output['paymentMethods'] = $this->getPaymentMethods(); diff --git a/app/code/Magento/Checkout/Model/Layout/AbstractTotalsProcessor.php b/app/code/Magento/Checkout/Model/Layout/AbstractTotalsProcessor.php index 34f3302154bab..a670482cb98d6 100644 --- a/app/code/Magento/Checkout/Model/Layout/AbstractTotalsProcessor.php +++ b/app/code/Magento/Checkout/Model/Layout/AbstractTotalsProcessor.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Checkout\Model\Layout; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; /** * Abstract totals processor. @@ -13,6 +15,7 @@ * Can be used to process totals information that will be rendered during checkout. * Abstract class provides sorting routing to sort total information based on configuration settings. * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ @@ -36,12 +39,14 @@ public function __construct( } /** + * Sort total information based on configuration settings. + * * @param array $totals * @return array */ public function sortTotals($totals) { - $configData = $this->scopeConfig->getValue('sales/totals_sort'); + $configData = $this->scopeConfig->getValue('sales/totals_sort', ScopeInterface::SCOPE_STORES); foreach ($totals as $code => &$total) { //convert JS naming style to config naming style $code = str_replace('-', '_', $code); diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php index e8ab07db184d0..bcc25bbbbf3cc 100644 --- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php @@ -112,6 +112,12 @@ public function savePaymentInformation( $quoteRepository = $this->getCartRepository(); /** @var \Magento\Quote\Model\Quote $quote */ $quote = $quoteRepository->getActive($cartId); + $customerId = $quote->getBillingAddress() + ->getCustomerId(); + if (!$billingAddress->getCustomerId() && $customerId) { + //It's necessary to verify the price rules with the customer data + $billingAddress->setCustomerId($customerId); + } $quote->removeAddress($quote->getBillingAddress()->getId()); $quote->setBillingAddress($billingAddress); $quote->setDataChanges(true); diff --git a/app/code/Magento/Checkout/Model/ShippingInformationManagement.php b/app/code/Magento/Checkout/Model/ShippingInformationManagement.php index 0adfbad3c8aeb..369ae8e6f725e 100644 --- a/app/code/Magento/Checkout/Model/ShippingInformationManagement.php +++ b/app/code/Magento/Checkout/Model/ShippingInformationManagement.php @@ -191,7 +191,9 @@ public function saveAddressInformation( $shippingAddress = $quote->getShippingAddress(); - if (!$shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod())) { + if (!$quote->getIsVirtual() + && !$shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod()) + ) { throw new NoSuchEntityException( __('Carrier with such method not found: %1, %2', $carrierCode, $methodCode) ); diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertAdminEmailValidationMessageOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertAdminEmailValidationMessageOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..858dbfc5e76f3 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertAdminEmailValidationMessageOnCheckoutActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEmailValidationMessageOnCheckoutActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Please enter a valid email address (Ex: johndoe@domain.com)."/> + </arguments> + <waitForElementVisible selector="{{AdminOrderFormAccountSection.emailErrorMessage}}" stepKey="waitForFormValidation"/> + <see selector="{{AdminOrderFormAccountSection.emailErrorMessage}}" userInput="{{message}}" stepKey="seeTheErrorMessageIsDisplayed"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertShoppingCartIsEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertShoppingCartIsEmptyActionGroup.xml new file mode 100644 index 0000000000000..d71832c63cb18 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertShoppingCartIsEmptyActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertShoppingCartIsEmptyActionGroup"> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <see userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefronElementVisibleActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefronElementVisibleActionGroup.xml new file mode 100644 index 0000000000000..b99c4b7f5bf16 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefronElementVisibleActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontElementVisibleActionGroup"> + <arguments> + <argument name="selector" type="string"/> + <argument name="userInput" type="string"/> + </arguments> + <see selector="{{selector}}" userInput="{{userInput}}" stepKey="assertElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.xml new file mode 100644 index 0000000000000..77a8bef9baf1c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontCheckoutCartItemsActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productSku" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="subtotal" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{productName}}" stepKey="seeProductNameInCheckoutSummary"/> + <see selector="{{CheckoutCartProductSection.checkoutCartProductPrice}}" userInput="{{productPrice}}" stepKey="seeProductPriceInCart"/> + <see selector="{{CheckoutCartProductSection.checkoutCartSubtotal}}" userInput="{{subtotal}}" stepKey="seeSubtotalPrice"/> + <seeInField selector="{{CheckoutCartProductSection.qtyByContains(productSku)}}" userInput="{{qty}}" stepKey="seeProductQuantity"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontElementInvisibleActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontElementInvisibleActionGroup.xml new file mode 100644 index 0000000000000..69fe27ff07460 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontElementInvisibleActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontElementInvisibleActionGroup"> + <arguments> + <argument name="selector" type="string"/> + <argument name="userInput" type="string"/> + </arguments> + <dontSee selector="{{selector}}" userInput="{{userInput}}" stepKey="dontSeeElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml new file mode 100644 index 0000000000000..258d8ed9021c0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontMiniCartItemsActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="cartSubtotal" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productName}}" stepKey="seeProductNameInMiniCart"/> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productPrice}}" stepKey="seeProductPriceInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="seeCheckOutButtonInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.productQuantity(productName, qty)}}" stepKey="seeProductQuantity1"/> + <seeElement selector="{{StorefrontMinicartSection.productImage}}" stepKey="seeProductImage"/> + <see selector="{{StorefrontMinicartSection.productSubTotal}}" userInput="{{cartSubtotal}}" stepKey="seeSubTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.xml new file mode 100644 index 0000000000000..02f00a8cd31a9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontSeeElementActionGroup"> + <arguments> + <argument name="selector" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <scrollTo selector="{{selector}}" stepKey="scrollToElement"/> + <seeElement selector="{{selector}}" stepKey="assertElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml new file mode 100644 index 0000000000000..9b963273b0409 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontShoppingCartSummaryItemsActionGroup"> + <arguments> + <argument name="subtotal" type="string"/> + <argument name="total" type="string"/> + </arguments> + <seeInCurrentUrl url="{{CheckoutCartPage.url}}" stepKey="assertUrl"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="waitForSubtotalVisible"/> + <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="{{subtotal}}" stepKey="assertSubtotal"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.total}}" stepKey="waitForTotalVisible"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.totalAmount(total)}}" stepKey="waitForTotalAmountVisible"/> + <see selector="{{CheckoutCartSummarySection.total}}" userInput="{{total}}" stepKey="assertTotal"/> + <seeElement selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="seeProceedToCheckoutButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml new file mode 100644 index 0000000000000..7d8406adc9039 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" extends="AssertStorefrontShoppingCartSummaryItemsActionGroup"> + <arguments> + <argument name="shipping" type="string"/> + </arguments> + <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" stepKey="waitForElementToBeVisible" after="assertSubtotal"/> + <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="30" stepKey="assertShipping" after="waitForElementToBeVisible"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutFillEstimateShippingAndTaxActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutFillEstimateShippingAndTaxActionGroup.xml new file mode 100644 index 0000000000000..f564e14989e75 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutFillEstimateShippingAndTaxActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CheckoutFillEstimateShippingAndTaxActionGroup"> + <arguments> + <argument name="address" defaultValue="US_Address_TX" type="entity"/> + </arguments> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.estimateShippingAndTaxSummary}}" visible="false" stepKey="openShippingDetails"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="{{address.country_id}}" stepKey="selectCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{address.state}}" stepKey="selectState"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.postcode}}" stepKey="waitForPostCodeVisible"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{address.postcode}}" stepKey="selectPostCode"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDiappear"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CustomerCheckoutFillNewShippingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CustomerCheckoutFillNewShippingAddressActionGroup.xml new file mode 100644 index 0000000000000..6a3d8810f42c9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CustomerCheckoutFillNewShippingAddressActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CustomerCheckoutFillNewShippingAddressActionGroup"> + <arguments> + <argument name="address" type="entity"/> + </arguments> + <selectOption selector="{{CheckoutShippingSection.country}}" userInput="{{address.country}}" stepKey="selectCounty"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{address.street}}" stepKey="fillStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{address.city}}" stepKey="fillCity"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{address.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{address.postcode}}" stepKey="fillZipCode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{address.telephone}}" stepKey="fillPhone"/> + <fillField selector="{{CheckoutShippingSection.company}}" userInput="{{address.company}}" stepKey="fillCompany"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/DeleteProductFromShoppingCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/DeleteProductFromShoppingCartActionGroup.xml new file mode 100644 index 0000000000000..86261e85d8d9a --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/DeleteProductFromShoppingCartActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details.z + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteProductFromShoppingCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{CheckoutCartProductSection.removeProductByName(productName)}}" stepKey="deleteProductFromCheckoutCart"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillGuestCheckoutShippingAddressFormActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillGuestCheckoutShippingAddressFormActionGroup.xml new file mode 100644 index 0000000000000..80fd604e752e9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillGuestCheckoutShippingAddressFormActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FillGuestCheckoutShippingAddressFormActionGroup"> + <arguments> + <argument name="customer" defaultValue="Simple_US_Customer" type="entity"/> + <argument name="customerAddress" defaultValue="US_Address_TX" type="entity"/> + </arguments> + <fillField selector="{{CheckoutShippingSection.emailAddress}}" userInput="{{customer.email}}" stepKey="setCustomerEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customer.firstname}}" stepKey="SetCustomerFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{customer.lastname}}" stepKey="SetCustomerLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{customerAddress.street[0]}}" stepKey="SetCustomerStreetAddress"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{customerAddress.city}}" stepKey="SetCustomerCity"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{customerAddress.postcode}}" stepKey="SetCustomerZipCode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{customerAddress.telephone}}" stepKey="SetCustomerPhoneNumber"/> + </actionGroup> + <actionGroup name="FillGuestCheckoutShippingAddressWithCountryActionGroup" extends="FillGuestCheckoutShippingAddressFormActionGroup"> + <selectOption selector="{{CheckoutShippingSection.country}}" userInput="{{customerAddress.country_id}}" stepKey="selectCustomerCountry" after="SetCustomerCity"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml index 34f2cfe7f7fff..59e997eccecc0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml @@ -70,6 +70,6 @@ <argument name="paymentMethod" type="string"/> </arguments> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" after="waitForLoading3" stepKey="waitForPaymentSectionLoaded"/> - <conditionalClick selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" dependentSelector="{{CheckoutPaymentSection.billingAddress}}" visible="false" parametrized="true" before="enterFirstName" stepKey="clickCheckMoneyOrderPayment"/> + <conditionalClick selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" dependentSelector="{{CheckoutPaymentSection.billingAddress}}" visible="false" before="enterFirstName" stepKey="clickCheckMoneyOrderPayment"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewShippingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewShippingAddressActionGroup.xml new file mode 100644 index 0000000000000..6ea2ccb08d8d3 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewShippingAddressActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="GuestCheckoutFillNewShippingAddressActionGroup"> + <arguments> + <argument name="customer" type="entity"/> + <argument name="address" type="entity"/> + </arguments> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{customer.email}}" stepKey="fillEmailField"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customer.firstName}}" stepKey="fillFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{customer.lastName}}" stepKey="fillLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{address.street}}" stepKey="fillStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{address.city}}" stepKey="fillCity"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{address.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{address.postcode}}" stepKey="fillZipCode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{address.telephone}}" stepKey="fillPhone"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerOnCheckoutPageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerOnCheckoutPageActionGroup.xml new file mode 100644 index 0000000000000..06a49b856b5e2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerOnCheckoutPageActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="LoginAsCustomerOnCheckoutPageActionGroup"> + <arguments> + <argument name="customer" type="entity"/> + </arguments> + <waitForPageLoad stepKey="waitForCheckoutShippingSectionToLoad"/> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{customer.email}}" stepKey="fillEmailField"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <waitForElementVisible selector="{{CheckoutShippingSection.password}}" stepKey="waitForElementVisible"/> + <fillField selector="{{CheckoutShippingSection.password}}" userInput="{{customer.password}}" stepKey="fillPasswordField"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <waitForElementVisible selector="{{CheckoutShippingSection.loginButton}}" stepKey="waitForLoginButtonVisible"/> + <doubleClick selector="{{CheckoutShippingSection.loginButton}}" stepKey="clickLoginBtn"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear3"/> + <waitForPageLoad stepKey="waitToBeLoggedIn"/> + <waitForElementNotVisible selector="{{CheckoutShippingSection.email}}" stepKey="waitForEmailInvisible" time ="60"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerUsingSignInLinkActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerUsingSignInLinkActionGroup.xml new file mode 100644 index 0000000000000..35e8e368ae300 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoginAsCustomerUsingSignInLinkActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="LoginAsCustomerUsingSignInLinkActionGroup"> + <arguments> + <argument name="customer" type="entity"/> + </arguments> + <click selector="{{StorefrontCustomerSignInLinkSection.signInLink}}" stepKey="clickOnCustomizeAndAddToCartButton"/> + <fillField selector="{{StorefrontCustomerSignInLinkSection.email}}" userInput="{{customer.email}}" stepKey="fillEmail"/> + <fillField selector="{{StorefrontCustomerSignInLinkSection.password}}" userInput="{{customer.password}}" stepKey="fillPassword"/> + <click selector="{{StorefrontCustomerSignInLinkSection.signInBtn}}" stepKey="clickSignInBtn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/OpenStoreFrontCheckoutShippingPageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/OpenStoreFrontCheckoutShippingPageActionGroup.xml new file mode 100644 index 0000000000000..cea9d968b58e1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/OpenStoreFrontCheckoutShippingPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="OpenStoreFrontCheckoutShippingPageActionGroup"> + <amOnPage url="{{CheckoutShippingPage.url}}" stepKey="amOnCheckoutShippingPage"/> + <waitForPageLoad stepKey="waitForCheckoutShippingPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddBundleProductToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddBundleProductToTheCartActionGroup.xml new file mode 100644 index 0000000000000..2c64eda38a420 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddBundleProductToTheCartActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddBundleProductToTheCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="quantity" type="string"/> + </arguments> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickOnCustomizeAndAddToCartButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection(productName)}}" stepKey="selectProduct"/> + <clearField selector="{{StorefrontBundleProductActionSection.quantityField}}" stepKey="clearTheQuantityField"/> + <fillField selector="{{StorefrontBundleProductActionSection.quantityField}}" userInput="{{quantity}}" stepKey="fillTheProductQuantity"/> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickOnAddToButton"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductToCartFromCategoryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductToCartFromCategoryActionGroup.xml new file mode 100644 index 0000000000000..de08fe7427c28 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductToCartFromCategoryActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddProductToCartFromCategoryActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <scrollTo selector="{{StorefrontCategoryProductSection.ProductInfoByName(productName)}}" stepKey="scroll"/> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(productName)}}" stepKey="moveMouseOverProduct" /> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(productName)}}" stepKey="clickAddToCart" /> + <waitForAjaxLoad stepKey="waitForAjax"/> + </actionGroup> +</actionGroups> + + diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml new file mode 100644 index 0000000000000..8e13748d64e0e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Add product with selected configurable option and customizable option to cart from the product page --> + <actionGroup name="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="option" type="string"/> + <argument name="customizableOption" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <click selector="{{StorefrontProductInfoMainSection.selectCustomOptionByName(customizableOption)}}" stepKey="selectCustomOption"/> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{product.name}} to your shopping cart." stepKey="assertSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml new file mode 100644 index 0000000000000..5d844523d4454 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Add product with selected option to Cart from the product page --> + <actionGroup name="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="option" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{product.name}} to your shopping cart." stepKey="assertSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToShopingCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToShopingCartActionGroup.xml new file mode 100644 index 0000000000000..14b1331c7ceb1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToShopingCartActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddSimpleProductToShoppingCartActionGroup"> + <arguments> + <argument name="product"/> + <argument name="qty" type="string" defaultValue="1" /> + </arguments> + <amOnPage url="{{StorefrontProductPage.url(product.custom_attributes[url_key])}}" stepKey="openProductPage" /> + <waitForPageLoad stepKey="waitForProductPageOpen" /> + <fillField userInput="{{qty}}" selector="{{StorefrontProductInfoMainSection.qty}}" stepKey="fillQty" /> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart" /> + <waitForElementVisible selector="{{StorefrontMessagesSection.messageByType('success')}}" stepKey="waitForSuccessMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml new file mode 100644 index 0000000000000..1603758016e0d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddToTheCartActionGroup"> + <waitForPageLoad stepKey="waitForPageLoad"/> + <scrollTo selector="{{StorefrontProductActionSection.addToCart}}" stepKey="scrollToAddToCartButton"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertMiniCartItemCountActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertMiniCartItemCountActionGroup.xml new file mode 100644 index 0000000000000..03caa558e4272 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertMiniCartItemCountActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertMiniCartItemCountActionGroup"> + <arguments> + <argument name="productCount" type="string"/> + <argument name="productCountText" type="string"/> + </arguments> + <see selector="{{StorefrontMinicartSection.productCount}}" userInput="{{productCount}}" stepKey="seeProductCountInCart"/> + <see selector="{{StorefrontMinicartSection.visibleItemsCountText}}" userInput="{{productCountText}}" stepKey="seeNumberOfItemDisplayInMiniCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertNoValidationErrorForCheckoutAddressFieldsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertNoValidationErrorForCheckoutAddressFieldsActionGroup.xml new file mode 100644 index 0000000000000..93e73a0c62b29 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertNoValidationErrorForCheckoutAddressFieldsActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertNoValidationErrorForCheckoutAddressFieldsActionGroup"> + <dontSeeElement selector="{{CheckoutShippingSection.addressFieldValidationError}}" stepKey="checkFieldsValidationIsPassed"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.xml new file mode 100644 index 0000000000000..59159ea2d0e7f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductDetailsInOrderSummaryActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="qty" type="string"/> + <argument name="price" type="string"/> + </arguments> + <see selector="{{CheckoutOrderSummarySection.productItemName}}" userInput="{{productName}}" stepKey="seeProductName"/> + <see selector="{{CheckoutOrderSummarySection.productItemQty}}" userInput="{{qty}}" stepKey="seeProductQty"/> + <see selector="{{CheckoutOrderSummarySection.productItemPrice}}" userInput="{{price}}" stepKey="seeProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.xml new file mode 100644 index 0000000000000..cd3d18e2aa09e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertShippingAddressPageDisplayActionGroup"> + <seeInCurrentUrl url="checkout/#shipping" stepKey="seeCurrentUrl"/> + <seeElement selector="{{CheckoutShippingSection.shippingTab}}" stepKey="seeShippingTab"/> + <seeElement selector="{{CheckoutShippingGuestInfoSection.shippingBlock}}" stepKey="seeShippingBlock"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodOptionPresentInCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodOptionPresentInCartActionGroup.xml new file mode 100644 index 0000000000000..6e033268934a5 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodOptionPresentInCartActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert shipping method name and price are present in cart --> + <actionGroup name="StorefrontAssertShippingMethodOptionPresentInCartActionGroup"> + <arguments> + <argument name="methodName" type="string"/> + <argument name="price" type="string"/> + </arguments> + <see selector="{{CheckoutCartSummarySection.methodName}}" userInput="{{methodName}}" stepKey="seeShippingName"/> + <see selector="{{CheckoutCartSummarySection.shippingPrice}}" userInput="{{price}}" stepKey="seeShippingPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAttachOptionFileActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAttachOptionFileActionGroup.xml new file mode 100644 index 0000000000000..364f05abd3882 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAttachOptionFileActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAttachOptionFileActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionFile"/> + <argument name="file" defaultValue="MagentoLogo.file" /> + </arguments> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(optionTitle.title)}}" userInput="{{file}}" stepKey="attachFile"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckCartDiscountAndSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckCartDiscountAndSummaryActionGroup.xml new file mode 100644 index 0000000000000..6c541db6defcc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckCartDiscountAndSummaryActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Open the Minicart and check Summary --> + <actionGroup name="StorefrontCheckCartDiscountAndSummaryActionGroup"> + <arguments> + <argument name="total" type="string"/> + <argument name="discount" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForLoadingMaskToDisappear stepKey="waitForPrices"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-${{discount}}" stepKey="assertDiscount"/> + <wait time="10" stepKey="waitForTotalPrice"/> + <see selector="{{CheckoutCartSummarySection.total}}" userInput="${{total}}" stepKey="assertTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.xml new file mode 100644 index 0000000000000..e5fbb9065f976 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckThatCartIsEmptyActionGroup"> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <see selector="{{StorefrontMinicartSection.messageEmptyCart}}" userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.xml new file mode 100644 index 0000000000000..0a29cba11d4a9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup"> + <arguments> + <argument name="itemsText" type="string"/> + </arguments> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="clickOnCheckOutButtonInMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <see selector="{{CheckoutShippingGuestInfoSection.itemInCart}}" userInput="{{itemsText}}" stepKey="seeOrderSummaryText"/> + <seeElement selector="{{CheckoutOrderSummarySection.miniCartTab}}" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.xml new file mode 100644 index 0000000000000..92ce4f1f360a6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckoutCheckOutOfStockProductActionGroup"> + <arguments> + <argument name="product" type="entity"/> + </arguments> + <see selector="{{CheckoutCartMessageSection.errorMessage}}" userInput="1 product requires your attention." stepKey="seeErrorMessage"/> + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{product.name}}" stepKey="seeProductName"/> + <see selector="{{CheckoutCartProductSection.messageErrorItem}}" userInput="Availability: Out of stock" stepKey="seeProductOutOfStock"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml new file mode 100644 index 0000000000000..a994611869274 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckoutCheckProductActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="cartItem" type="entity"/> + </arguments> + + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{product.name}}" stepKey="seeProductName"/> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName(product.name)}}" stepKey="grabProductQty"/> + <assertEquals expected="{{cartItem.qty}}" actual="$grabProductQty" stepKey="assertQtyShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml new file mode 100644 index 0000000000000..09e991c21645d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickOnMiniCartActionGroup"> + <scrollToTopOfPage stepKey="scrollToTheTopOfThePage"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCustomerSignInPopUpActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCustomerSignInPopUpActionGroup.xml new file mode 100644 index 0000000000000..8139ac1f7929b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCustomerSignInPopUpActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerSignInPopUpActionGroup"> + <arguments> + <argument name="customerEmail" type="string"/> + <argument name="customerPwd" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitForElementToBeVisible"/> + <fillField stepKey="fillEmail" userInput="{{customerEmail}}" selector="{{StorefrontCustomerSignInPopupFormSection.email}}"/> + <fillField stepKey="fillPassword" userInput="{{customerPwd}}" selector="{{StorefrontCustomerSignInPopupFormSection.password}}"/> + <click stepKey="clickSignInAccountButton" selector="{{StorefrontCustomerSignInPopupFormSection.signIn}}"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.xml new file mode 100644 index 0000000000000..c76cb8c0f1f88 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontEnterProductQuantityAndAddToTheCartActionGroup"> + <arguments> + <argument name="quantity" type="string"/> + </arguments> + <clearField selector="{{StorefrontBundleProductActionSection.quantityField}}" stepKey="clearTheQuantityField"/> + <fillField selector="{{StorefrontBundleProductActionSection.quantityField}}" userInput="{{quantity}}" stepKey="fillTheProductQuantity"/> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickOnAddToButton"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionFieldInputActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionFieldInputActionGroup.xml new file mode 100644 index 0000000000000..001cdae5a4793 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionFieldInputActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillOptionFieldInputActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionField"/> + <argument name="fieldInput" type="string" /> + </arguments> + <fillField selector="{{StorefrontProductInfoMainSection.productOptionFieldInput(optionTitle.title)}}" userInput="{{fieldInput}}" stepKey="fillOptionField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.xml new file mode 100644 index 0000000000000..1e3162bac3bfa --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillOptionTextAreaActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionArea"/> + <argument name="fieldInput" type="string"/> + </arguments> + <fillField selector="{{StorefrontProductInfoMainSection.productOptionAreaInput(optionTitle.title)}}" userInput="{{fieldInput}}" stepKey="fillOptionTextArea"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml index e4a388d2c3a58..28e109e2387a1 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml @@ -8,9 +8,11 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="clickViewAndEditCartFromMiniCart"> + <scrollTo selector="{{StorefrontMinicartSection.showCart}}" stepKey="scrollToMiniCart"/> <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart"/> <waitForElementVisible selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="waitForViewAndEditCartVisible"/> <click selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="viewAndEditCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> <seeInCurrentUrl url="checkout/cart" stepKey="seeInCurrentUrl"/> </actionGroup> <actionGroup name="assertOneProductNameInMiniCart"> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenMiniCartActionGroup.xml new file mode 100644 index 0000000000000..7afcb070f3f06 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenMiniCartActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenMiniCartActionGroup"> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index 24ed05583b6fb..fb3f295836161 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -53,7 +53,6 @@ <seeLink stepKey="assertLinkToShoppingCart" url="{{_ENV.MAGENTO_BASE_URL}}checkout/cart/" userInput="shopping cart" /> <waitForText userInput="{{productCount}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> </actionGroup> - <!-- Open the Minicart and check Simple Product --> <actionGroup name="StorefrontOpenMinicartAndCheckSimpleProductActionGroup"> <arguments> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontRegisterCustomerAfterCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontRegisterCustomerAfterCheckoutActionGroup.xml new file mode 100644 index 0000000000000..7546e04759503 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontRegisterCustomerAfterCheckoutActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontRegisterCustomerAfterCheckoutActionGroup"> + <arguments> + <argument name="customerPassword" defaultValue="UKCustomer.password" type="string"/> + </arguments> + <click selector="{{CheckoutSuccessRegisterSection.createAccountButton}}" stepKey="clickOnCreateAnAccountButton"/> + <fillField selector="{{StorefrontCustomerAccountInformationSection.newPassword}}" userInput="{{customerPassword}}" stepKey="fillPassword"/> + <fillField selector="{{StorefrontCustomerAccountInformationSection.confirmNewPassword}}" userInput="{{customerPassword}}" stepKey="reconfirmPassword"/> + <click selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" stepKey="clickOnCreateAnAccount"/> + <seeElement selector="{{StorefrontCustomerMessagesSection.successMessage}}" stepKey="seeSuccessMessage1"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.xml new file mode 100644 index 0000000000000..731ac03ca414f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionCheckBoxActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionValueCheckbox"/> + </arguments> + <checkOption selector="{{StorefrontProductInfoMainSection.customOptionCheckBox(optionTitle.title)}}" stepKey="SelectOptionRadioButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml new file mode 100644 index 0000000000000..37f2afd2a00b0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionDateActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDate"/> + <argument name="month" type="string"/> + <argument name="day" type="string"/> + <argument name="year" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMonth(optionTitle.title)}}" userInput="{{month}}" stepKey="selectMonthForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDay(optionTitle.title)}}" userInput="{{day}}" stepKey="selectDayForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionYear(optionTitle.title)}}" userInput="{{year}}" stepKey="selectYearForOptionDate"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml new file mode 100644 index 0000000000000..f2bac08a381d6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionDateTimeActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDateTime"/> + <argument name="month" type="string"/> + <argument name="day" type="string"/> + <argument name="year" type="string"/> + <argument name="hour" type="string"/> + <argument name="minute" type="string"/> + <argument name="dayPart" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMonth(optionTitle.title)}}" userInput="{{month}}" stepKey="selectMonthForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDay(optionTitle.title)}}" userInput="{{day}}" stepKey="selectDayForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionYear(optionTitle.title)}}" userInput="{{year}}" stepKey="selectYearForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionHour(optionTitle.title)}}" userInput="{{hour}}" stepKey="selectHourrForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMinute(optionTitle.title)}}" userInput="{{minute}}" stepKey="selectMinuteForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDayPart(optionTitle.title)}}" userInput="{{dayPart}}" stepKey="selectDayPartForOptionDateTime"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml new file mode 100644 index 0000000000000..1f8cedae4efb1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionDropDownActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDropDown"/> + <argument name="option" defaultValue="ProductOptionValueDropdown2.title" /> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(optionTitle.title)}}" userInput="{{option}}" stepKey="fillOptionDropDown"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.xml new file mode 100644 index 0000000000000..ebf14b3f27d12 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionMultiSelectActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionMultiSelect"/> + <argument name="option" defaultValue="ProductOptionValueMultiSelect2.title"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(optionTitle.title)}}" userInput="{{option}}" stepKey="selectOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml new file mode 100644 index 0000000000000..844c9ede80232 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionRadioButtonActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionRadiobutton"/> + <argument name="optionPrice" defaultValue="ProductOptionValueRadioButtons2"/> + </arguments> + <checkOption selector="{{StorefrontProductInfoMainSection.productOptionRadioButtonsCheckbox(optionTitle.title, optionPrice.price)}}" stepKey="SelectOptionCheckBox"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml new file mode 100644 index 0000000000000..bfa75eae70251 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSelectOptionTimeActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionTime"/> + <argument name="hour" type="string"/> + <argument name="minute" type="string"/> + <argument name="dayPart" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionHour(optionTitle.title)}}" userInput="{{hour}}" stepKey="selectHourrForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMinute(optionTitle.title)}}" userInput="{{minute}}" stepKey="selectMinuteForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDayPart(optionTitle.title)}}" userInput="{{dayPart}}" stepKey="selectDayPartForOptionDateTime"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml new file mode 100644 index 0000000000000..797a712c6476f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Fill shipment form for free shipping--> + <actionGroup name="VerifyCheckoutPaymentOrderSummaryActionGroup"> + <arguments> + <argument name="orderSummarySubTotal" type="string"/> + <argument name="orderSummaryShippingTotal" type="string"/> + <argument name="orderSummaryTotal" type="string"/> + </arguments> + <see selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" userInput="{{orderSummarySubTotal}}" stepKey="seeCorrectSubtotal"/> + <see selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" userInput="{{orderSummaryShippingTotal}}" stepKey="seeCorrectShipping"/> + <see selector="{{CheckoutPaymentSection.orderSummaryTotal}}" userInput="{{orderSummaryTotal}}" stepKey="seeCorrectOrderTotal"/> + </actionGroup> + <!-- Assert Order Summary SubTotal You should be on checkout page --> + <actionGroup name="AssertStorefrontCheckoutPaymentSummarySubtotalActionGroup"> + <arguments> + <argument name="orderSubtotal" type="string"/> + </arguments> + <waitForPageLoad time="30" stepKey="waitForCartFullyLoaded"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" time="30" stepKey="waitForOrderSummaryBlock"/> + <see selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" userInput="{{orderSubtotal}}" stepKey="seeCorrectSubtotal"/> + </actionGroup> + <!-- Assert Order Summary Total You should be on checkout page --> + <actionGroup name="AssertStorefrontCheckoutPaymentSummaryTotalActionGroup"> + <arguments> + <argument name="orderTotal" type="string"/> + </arguments> + <waitForPageLoad time="30" stepKey="waitForCartFullyLoaded"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.orderSummaryTotal}}" time="30" stepKey="waitForOrderSummaryBlock"/> + <see selector="{{CheckoutPaymentSection.orderSummaryTotal}}" userInput="{{orderTotal}}" stepKey="seeCorrectOrderTotal"/> + </actionGroup> + <!-- Assert Order Summary Total Is Not Shown You should be on checkout page --> + <actionGroup name="AssertStorefrontCheckoutPaymentSummaryTotalMissingActionGroup"> + <waitForPageLoad time="30" stepKey="waitForCartFullyLoaded"/> + <dontSeeElement selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="seeTotalElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/CheckoutConfigData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/CheckoutConfigData.xml new file mode 100644 index 0000000000000..13d9385101f18 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Data/CheckoutConfigData.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableGuestCheckoutConfigData"> + <data key="path">checkout/options/guest_checkout</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableGuestCheckoutConfigData"> + <data key="path">checkout/options/guest_checkout</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="EnableHttpsFrontendConfigData"> + <data key="path">web/secure/use_in_frontend</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableHttpsFrontendConfigData"> + <data key="path">web/secure/use_in_frontend</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/ConfigData.xml new file mode 100644 index 0000000000000..bb47a2fcc3070 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Data/ConfigData.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <!-- Free shipping --> + <entity name="EnableFreeShippingConfigData"> + <data key="path">carriers/freeshipping/active</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="EnableFreeShippingToSpecificCountriesConfigData"> + <data key="path">carriers/freeshipping/sallowspecific</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Specific Countries</data> + <data key="value">1</data> + </entity> + <entity name="EnableFreeShippingToAfghanistanConfigData"> + <data key="path">carriers/freeshipping/specificcountry</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Afghanistan</data> + <data key="value">AF</data> + </entity> + <entity name="EnableFreeShippingToAllAllowedCountriesConfigData"> + <data key="path">carriers/freeshipping/sallowspecific</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">All Allowed Countries</data> + <data key="value">0</data> + </entity> + <entity name="DisableFreeShippingConfigData"> + <data key="path">carriers/freeshipping/active</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + + <!-- Flat Rate shipping --> + <entity name="EnableFlatRateConfigData"> + <data key="path">carriers/flatrate/active</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="EnableFlatRateDefaultPriceConfigData"> + <data key="path">carriers/flatrate/price</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">5.00</data> + </entity> + <entity name="EnableFlatRateToSpecificCountriesConfigData"> + <data key="path">carriers/flatrate/sallowspecific</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Specific Countries</data> + <data key="value">1</data> + </entity> + <entity name="EnableFlatRateToAfghanistanConfigData"> + <data key="path">carriers/flatrate/specificcountry</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">Afghanistan</data> + <data key="value">AF</data> + </entity> + <entity name="EnableFlatRateToAllAllowedCountriesConfigData"> + <data key="path">carriers/flatrate/sallowspecific</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">All Allowed Countries</data> + <data key="value">0</data> + </entity> + <entity name="DisableFlatRateConfigData"> + <data key="path">carriers/flatrate/active</data> + <data key="scope">carriers</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + + <!-- Checkout Config: Display Cart Summary --> + <entity name="DisplayItemsQuantities"> + <data key="path">checkout/cart_link/use_qty</data> + <data key="label">Display items quantities</data> + <data key="value">1</data> + </entity> + <entity name="DisplayUniqueItems"> + <data key="path">checkout/cart_link/use_qty</data> + <data key="label">Display number of items in cart</data> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml index e7a5992ad8943..7c4f814e2b977 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml @@ -37,4 +37,21 @@ <data key="subtotal">1,320.00</data> <data key="currency">$</data> </entity> + <entity name="quoteQty2Price123" type="Quote"> + <data key="price">123.00</data> + <data key="qty">2</data> + <data key="shipping">10.00</data> + <data key="subtotal">246.00</data> + <data key="total">256.00</data> + <data key="currency">$</data> + </entity> + <entity name="quoteQty2Subtotal266" type="Quote"> + <data key="qty">2</data> + <data key="customOptionsPrice">20</data> + <data key="price">143</data> + <data key="subtotal">266.00</data> + <data key="shipping">10.00</data> + <data key="total">276.00</data> + <data key="currency">$</data> + </entity> </entities> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml index bf17800f29ad1..a77b07a129dce 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml @@ -12,5 +12,6 @@ <section name="CheckoutCartProductSection"/> <section name="CheckoutCartSummarySection"/> <section name="CheckoutCartCrossSellSection"/> + <section name="CheckoutCartMessageSection"/> </page> </pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml new file mode 100644 index 0000000000000..cf15cdf15cf15 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CheckoutCartMessageSection"> + <element name="successMessage" type="text" selector=".message.message-success.success>div" /> + <element name="errorMessage" type="text" selector=".message-error.error.message>div" /> + <element name="emptyCartMessage" type="text" selector=".cart-empty>p"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml index dcfb12fd4e965..3ab3fa5857b78 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml @@ -15,6 +15,9 @@ <element name="ProductPriceByName" type="text" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'price')]//span[@class='price']" parameterized="true"/> + <element name="ProductRegularPriceByName" type="text" + selector="//div[descendant::*[contains(text(), '{{var1}}')]]//*[contains(@class, 'subtotal')]" + parameterized="true"/> <element name="ProductImageByName" type="text" selector="//main//table[@id='shopping-cart-table']//tbody//tr//img[contains(@class, 'product-image-photo') and @alt='{{var1}}']" parameterized="true"/> @@ -24,8 +27,10 @@ <element name="ProductOptionByNameAndAttribute" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[.//strong[contains(@class, 'product-item-name')]//a[contains(text(), '{{var1}}')]]//dl[@class='item-options']//dt[.='{{var2}}']/following-sibling::dd[1]" parameterized="true"/> + <element name="ProductPriceByOption" type="text" selector="//*[contains(@class, 'item-options')]/dd[normalize-space(.)='{{var1}}']/ancestor::tr//td[contains(@class, 'price')]//span[@class='price']" parameterized="true"/> <element name="RemoveItem" type="button" selector="//table[@id='shopping-cart-table']//tbody//tr[contains(@class,'item-actions')]//a[contains(@class,'action-delete')]"/> + <element name="removeProductByName" type="text" selector="//*[contains(text(), '{{productName}}')]/ancestor::tbody//a[@class='action action-delete']" parameterized="true" timeout="30"/> <element name="productName" type="text" selector="//tbody[@class='cart item']//strong[@class='product-item-name']"/> <element name="nthItemOption" type="block" selector=".item:nth-of-type({{numElement}}) .item-options" parameterized="true"/> <element name="nthEditButton" type="block" selector=".item:nth-of-type({{numElement}}) .action-edit" parameterized="true"/> @@ -33,5 +38,16 @@ <element name="productSubtotalByName" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'subtotal')]//span[@class='price']" parameterized="true"/> <element name="updateShoppingCartButton" type="button" selector="#form-validate button[type='submit'].update" timeout="30"/> <element name="qty" type="input" selector="//input[@data-cart-item-id='{{var}}'][@title='Qty']" parameterized="true"/> + <element name="qtyByContains" type="text" selector="//input[contains(@data-cart-item-id, '{{sku}}')][@title='Qty']" parameterized="true"/> + <element name="messageErrorItem" type="text" selector="#sku-stock-failed-"/> + <element name="messageErrorNeedChooseOptions" type="text" selector="//*[contains(@class, 'error')]/div"/> + <element name="productOptionsLink" type="text" selector="a.action.configure"/> + <element name="productOptionLabel" type="text" selector="//dl[@class='item-options']"/> + <element name="checkoutCartProductPrice" type="text" selector="//td[@class='col price']//span[@class='price']"/> + <element name="checkoutCartSubtotal" type="text" selector="//td[@class='col subtotal']//span[@class='price']"/> + <element name="emptyCart" selector=".cart-empty" type="text"/> + <!-- Required attention section --> + <element name="removeProductBySku" type="button" selector="//div[contains(., '{{sku}}')]/ancestor::tbody//button" parameterized="true" timeout="30"/> + <element name="failedItemBySku" type="block" selector="//div[contains(.,'{{sku}}')]/ancestor::tbody" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml index 3100fae3b119b..477451ef003ce 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -9,15 +9,18 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutCartSummarySection"> + <element name="expandShoppingCartSummary" type="button" selector="//*[contains(@class, 'items-in-cart')][not(contains(@class, 'active'))]"/> <element name="elementPosition" type="text" selector=".data.table.totals > tbody tr:nth-of-type({{value}}) > th" parameterized="true"/> <element name="subtotal" type="text" selector="//*[@id='cart-totals']//tr[@class='totals sub']//td//span[@class='price']"/> <element name="shippingMethodForm" type="text" selector="#co-shipping-method-form"/> <element name="shippingMethod" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//th//span[@class='value']"/> <element name="shipping" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//td//span[@class='price']"/> + <element name="shippingAmount" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//td//span[@class='price' and contains(text(), '{{amount}}')]" parameterized="true"/> <element name="total" type="text" selector="//*[@id='cart-totals']//tr[@class='grand totals']//td//span[@class='price']"/> + <element name="totalAmount" type="text" selector="//*[@id='cart-totals']//tr[@class='grand totals']//td//span[@class='price' and contains(text(), '{{amount}}')]" parameterized="true"/> <element name="proceedToCheckout" type="button" selector=".action.primary.checkout span" timeout="30"/> <element name="discountAmount" type="text" selector="td[data-th='Discount']"/> - <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> + <element name="shippingHeading" type="button" selector="#block-shipping-heading" timeout="60"/> <element name="postcode" type="input" selector="input[name='postcode']" timeout="10"/> <element name="stateProvince" type="select" selector="select[name='region_id']" timeout="10"/> <element name="stateProvinceInput" type="input" selector="input[name='region']"/> @@ -25,7 +28,10 @@ <element name="countryParameterized" type="select" selector="select[name='country_id'] > option:nth-child({{var}})" timeout="10" parameterized="true"/> <element name="estimateShippingAndTax" type="text" selector="#block-shipping-heading" timeout="5"/> <element name="flatRateShippingMethod" type="input" selector="#s_method_flatrate_flatrate" timeout="30"/> + <element name="estimateShippingAndTaxSummary" type="text" selector="#block-summary"/> <element name="shippingMethodLabel" type="text" selector="#co-shipping-method-form dl dt span"/> + <element name="methodName" type="text" selector="#co-shipping-method-form label"/> + <element name="shippingPrice" type="text" selector="#co-shipping-method-form span .price"/> <element name="shippingMethodElementId" type="radio" selector="#s_method_{{carrierCode}}_{{methodCode}}" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml index 6e00329901757..d3ad2aed96946 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml @@ -17,5 +17,7 @@ <element name="shippingAddress" type="textarea" selector="//*[@class='box box-address-shipping']//address"/> <element name="billingAddress" type="textarea" selector="//*[@class='box box-address-billing']//address"/> <element name="additionalAddress" type="text" selector=".block.block-addresses-list"/> + <element name="miniCartTabClosed" type="button" selector=".title[aria-expanded='false']" timeout="30"/> + <element name="itemsQtyInCart" type="text" selector=".items-in-cart > .title > strong > span"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index cbe71e9cffa60..903c21d7ec0ca 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -14,15 +14,17 @@ <element name="notAvailablePaymentSolutions" type="text" selector="#checkout-payment-method-load>div>div>div.payment-method._active>div.payment-method-title.field.choice"/> <element name="billingNewAddressForm" type="text" selector="[data-form='billing-new-address']"/> <element name="billingAddressNotSameCheckbox" type="checkbox" selector="#billing-address-same-as-shipping-checkmo"/> + <element name="editAddress" type="button" selector="button.action.action-edit-address"/> <element name="placeOrderDisabled" type="button" selector="#checkout-payment-method-load button.disabled"/> - <element name="update" type="button" selector=".payment-method-billing-address .action.action-update"/> - <element name="guestFirstName" type="input" selector=".billing-address-form input[name*='firstname']"/> - <element name="guestLastName" type="input" selector=".billing-address-form input[name*='lastname']"/> - <element name="guestStreet" type="input" selector=".billing-address-form input[name*='street[0]']"/> - <element name="guestCity" type="input" selector=".billing-address-form input[name*='city']"/> - <element name="guestRegion" type="select" selector=".billing-address-form select[name*='region_id']"/> - <element name="guestPostcode" type="input" selector=".billing-address-form input[name*='postcode']"/> - <element name="guestTelephone" type="input" selector=".billing-address-form input[name*='telephone']"/> + <element name="update" type="button" selector=".payment-method._active .payment-method-billing-address .action.action-update"/> + <element name="guestFirstName" type="input" selector=".payment-method._active .billing-address-form input[name='firstname']"/> + <element name="guestLastName" type="input" selector=".payment-method._active .billing-address-form input[name*='lastname']"/> + <element name="guestStreet" type="input" selector=".payment-method._active .billing-address-form input[name*='street[0]']"/> + <element name="guestCity" type="input" selector=".payment-method._active .billing-address-form input[name*='city']"/> + <element name="guestCountry" type="select" selector=".payment-method._active .billing-address-form select[name*='country_id']"/> + <element name="guestRegion" type="select" selector=".payment-method._active .billing-address-form select[name*='region_id']"/> + <element name="guestPostcode" type="input" selector=".payment-method._active .billing-address-form input[name*='postcode']"/> + <element name="guestTelephone" type="input" selector=".payment-method._active .billing-address-form input[name*='telephone']"/> <element name="billingAddress" type="text" selector=".payment-method._active div.billing-address-details"/> <element name="cartItems" type="text" selector="ol.minicart-items"/> <element name="cartItemsArea" type="button" selector="div.block.items-in-cart"/> @@ -44,7 +46,6 @@ <element name="productOptionsByProductItemPrice" type="text" selector="//div[@class='product-item-inner']//div[@class='subtotal']//span[@class='price'][contains(.,'{{price}}')]//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true"/> <element name="productOptionsActiveByProductItemPrice" type="text" selector="//div[@class='subtotal']//span[@class='price'][contains(.,'{{price}}')]//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true"/> <element name="productItemPriceByName" type="text" selector="//div[@class='product-item-details'][contains(., '{{ProductName}}')]//span[@class='price']" parameterized="true"/> - <element name="tax" type="text" selector="[data-th='Tax'] span" timeout="30"/> <element name="taxPercentage" type="text" selector=".totals-tax-details .mark"/> <element name="orderSummaryTotalIncluding" type="text" selector="//tr[@class='grand totals incl']//span[@class='price']" /> @@ -54,6 +55,10 @@ <element name="addressBook" type="button" selector="//a[text()='Address Book']"/> <element name="noQuotes" type="text" selector=".no-quotes-block"/> <element name="paymentMethodByName" type="text" selector="//*[@id='checkout-payment-method-load']//*[contains(@class, 'payment-group')]//label[normalize-space(.)='{{var1}}']" parameterized="true"/> + <element name="bankTransfer" type="radio" selector="#banktransfer"/> + <element name="billingAddressNotSameBankTransferCheckbox" type="checkbox" selector="#billing-address-same-as-shipping-banktransfer"/> <element name="billingAddressSelectShared" type="select" selector=".checkout-billing-address select[name='billing_address_id']"/> + <element name="discount" type="block" selector="tr.totals.discount"/> + <element name="discountPrice" type="text" selector=".discount .price"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml index c97a8f291e941..c39fab8a52e8e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml @@ -12,6 +12,7 @@ <element name="email" type="input" selector="#customer-email"/> <element name="firstName" type="input" selector="input[name=firstname]"/> <element name="lastName" type="input" selector="input[name=lastname]"/> + <element name="company" type="input" selector="input[name=company]"/> <element name="street" type="input" selector="input[name='street[0]']"/> <element name="street2" type="input" selector="input[name='street[1]']"/> <element name="city" type="input" selector="input[name=city]"/> @@ -19,10 +20,10 @@ <element name="regionInput" type="input" selector="input[name=region]"/> <element name="postcode" type="input" selector="input[name=postcode]"/> <element name="country" type="select" selector="select[name=country_id]"/> - <element name="company" type="input" selector="input[name=company]"/> <element name="telephone" type="input" selector="input[name=telephone]"/> <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> <element name="firstShippingMethod" type="radio" selector=".row:nth-of-type(1) .col-method .radio"/> + <element name="shippingBlock" type="text" selector="#checkout-step-shipping"/> <!--Order Summary--> <element name="itemInCart" type="button" selector="//div[@class='title']"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml index 77d903eab3934..2f49e4f422a6e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml @@ -14,9 +14,11 @@ <element name="shippingMethodRow" type="text" selector=".form.methods-shipping table tbody tr"/> <element name="checkShippingMethodByName" type="radio" selector="//div[@id='checkout-shipping-method-load']//td[contains(., '{{var1}}')]/..//input" parameterized="true"/> <element name="shippingMethodFlatRate" type="radio" selector="#checkout-shipping-method-load input[value='flatrate_flatrate']"/> + <element name="shippingMethodFreeShipping" type="radio" selector="#checkout-shipping-method-load input[value='freeshipping_freeshipping']" timeout="60"/> <element name="shippingMethodRowByName" type="text" selector="//div[@id='checkout-shipping-method-load']//td[contains(., '{{var1}}')]/.." parameterized="true"/> <element name="shipHereButton" type="button" selector="//div/following-sibling::div/button[contains(@class, 'action-select-shipping-item')]"/> <element name="shippingMethodLoader" type="button" selector="//div[contains(@class, 'checkout-shipping-method')]/following-sibling::div[contains(@class, 'loading-mask')]"/> <element name="freeShippingShippingMethod" type="input" selector="#s_method_freeshipping_freeshipping" timeout="30"/> + <element name="noQuotesMsg" type="text" selector="#checkout-step-shipping_method div"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index d825e10395145..59d46e8cca696 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -15,7 +15,8 @@ <element name="editAddressButton" type="button" selector=".action-edit-address" timeout="30"/> <element name="addressDropdown" type="select" selector="[name=billing_address_id]"/> <element name="newAddressButton" type="button" selector=".action-show-popup" timeout="30"/> - <element name="email" type="input" selector="#customer-email"/> + <element name="email" type="input" selector="input[id*=customer-email]"/> + <element name="password" type="input" selector="#customer-password"/> <element name="firstName" type="input" selector="input[name=firstname]"/> <element name="lastName" type="input" selector="input[name=lastname]"/> <element name="company" type="input" selector="input[name=company]"/> @@ -36,5 +37,12 @@ <element name="stateInput" type="input" selector="input[name=region]"/> <element name="regionOptions" type="select" selector="select[name=region_id] option"/> <element name="editActiveAddress" type="button" selector="//div[@class='shipping-address-item selected-item']//span[text()='Edit']" timeout="30"/> + <element name="loginButton" type="button" selector="//button[@data-action='checkout-method-login']" timeout="30"/> + <element name="editActiveAddressButton" type="button" selector="//div[contains(@class,'payment-method _active')]//button[contains(@class,'action action-edit-address')]" timeout="30"/> + <element name="emailAddress" type="input" selector="#customer-email"/> + <element name="shipHereButton" type="button" selector="//div[text()='{{street}}']/button[@class='action action-select-shipping-item']" parameterized="true" timeout="30"/> + <element name="addressFieldValidationError" type="text" selector="div.address div.field .field-error"/> + <element name="textFieldAttrRequireMessage" type="text" selector="//input[@name='custom_attributes[{{attribute}}]']/ancestor::div[contains(@class, 'control')]/div/span" parameterized="true" timeout="30"/> + <element name="textFieldAttribute" type="input" selector="[name*='custom_attributes[{{attribute}}]']" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml index bc65f8a2c0816..08a9d671a8d02 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml @@ -16,7 +16,8 @@ <element name="orderLink" type="text" selector="a[href*=order_id].order-number" timeout="30"/> <element name="orderNumberText" type="text" selector=".checkout-success > p:nth-child(1)"/> <element name="continueShoppingButton" type="button" selector=".action.primary.continue" timeout="30"/> - <element name="createAnAccount" type="button" selector="input[value='Create an Account']" timeout="30"/> + <element name="createAnAccount" type="button" selector="[data-bind*="i18n: 'Create an Account'"]" timeout="30"/> <element name="printLink" type="button" selector=".print" timeout="30"/> + <element name="orderNumberWithoutLink" type="text" selector="//div[contains(@class, 'checkout-success')]//p/span"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml index 0d692e4ab143e..baee6cc7177c8 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml @@ -11,7 +11,7 @@ <section name="CheckoutSuccessRegisterSection"> <element name="registerMessage" type="text" selector="#registration p:nth-child(1)"/> <element name="customerEmail" type="text" selector="#registration p:nth-child(2)"/> - <element name="createAccountButton" type="button" selector="#registration form input[type='submit']" timeout="30"/> + <element name="createAccountButton" type="button" selector="[data-bind*="i18n: 'Create an Account'"]" timeout="30"/> <element name="orderNumber" type="text" selector="//p[text()='Your order # is: ']//span"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml index 9d9a96d2ea5e6..40214b9c11fb0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml @@ -13,5 +13,8 @@ <element name="checkPaymentMethodByName" type="radio" selector="//div[@id='checkout-payment-method-load']//div[@class='payment-method']//label//span[contains(., '{{methodName}}')]/../..//input" parameterized="true"/> <element name="billingAddressSameAsShipping" type="checkbox" selector=".payment-method._active [name='billing-address-same-as-shipping']"/> <element name="billingAddressSameAsShippingShared" type="checkbox" selector="#billing-address-same-as-shipping-shared"/> + <element name="paymentOnAccount" type="radio" selector="#companycredit"/> + <element name="paymentOnAccountLabel" type="text" selector="//span[text()='Payment on Account']"/> + <element name="purchaseOrderNumber" type="input" selector="#po_number"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml index 38c88bf4f80bb..621cd1d3a7bae 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -16,7 +16,7 @@ <element name="productName" type="text" selector=".product-item-name"/> <element name="productOptionsDetailsByName" type="button" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details'][.//a[contains(text(), '{{var1}}')]]//span[.='See Details']" parameterized="true"/> <element name="productOptionByNameAndAttribute" type="text" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details'][.//a[contains(text(), '{{var1}}')]]//dt[@class='label' and .='{{var2}}']/following-sibling::dd[@class='values']//span" parameterized="true"/> - <element name="showCart" type="button" selector="a.showcart"/> + <element name="showCart" type="button" selector="a.showcart" timeout="60"/> <element name="quantity" type="button" selector="span.counter-number"/> <element name="miniCartOpened" type="button" selector="a.showcart.active"/> <element name="goToCheckout" type="button" selector="#top-cart-btn-checkout" timeout="30"/> @@ -33,5 +33,10 @@ <element name="subtotal" type="text" selector="//tr[@class='totals sub']//td[@class='amount']/span"/> <element name="emptyCart" type="text" selector=".counter.qty.empty"/> <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> + <element name="messageEmptyCart" type="text" selector="//*[@id='minicart-content-wrapper']//*[contains(@class,'subtitle empty')]"/> + <element name="visibleItemsCountText" type="text" selector="//div[@class='items-total']"/> + <element name="productQuantity" type="input" selector="//*[@id='mini-cart']//a[contains(text(),'{{productName}}')]/../..//div[@class='details-qty qty']//input[@data-item-qty='{{qty}}']" parameterized="true"/> + <element name="productImage" type="text" selector="//ol[@id='mini-cart']//img[@class='product-image-photo']"/> + <element name="productSubTotal" type="text" selector="//div[@class='subtotal']//span/span[@class='price']"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml new file mode 100644 index 0000000000000..c1bee4af4c29b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontProductCartGiftOptionSection"> + <element name="giftOptions" type="button" selector=".action.action-gift"/> + <element name="fieldTo" type="input" selector=".gift-options-content .field-to input"/> + <element name="fieldFrom" type="input" selector=".gift-options-content .field-from input"/> + <element name="message" type="textarea" selector="#gift-message-whole-message"/> + <element name="update" type="button" selector=".action-update"/> + <element name="cancel" type="button" selector=".action-cancel"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 95929401620b8..29a1b72947c06 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="AddToCart" type="button" selector="#product-addtocart-button"/> + <element name="updateCart" type="button" selector="#product-updatecart-button" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml index 71a0c7f7fbdb3..f3e31d23f715b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml @@ -12,10 +12,10 @@ <annotations> <features value="Checkout"/> <stories value="MAGETWO-91465"/> - <title value="Guest Checkout"/> + <title value="Guest Checkout - address State field should not allow just integer values"/> <description value="Address State field should not allow just integer values"/> <severity value="MAJOR"/> - <testCaseId value="MAGETWO-93203"/> + <testCaseId value="MC-6223"/> <group value="checkout"/> </annotations> <before> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckConfigsChangesIsNotAffectedStartedCheckoutProcessTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckConfigsChangesIsNotAffectedStartedCheckoutProcessTest.xml new file mode 100644 index 0000000000000..31a9d011a91c7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckConfigsChangesIsNotAffectedStartedCheckoutProcessTest.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckConfigsChangesAreNotAffectedStartedCheckoutProcessTest"> + <annotations> + <features value="Checkout"/> + <stories value="Changes in configs are not affecting checkout process"/> + <title value="Admin check configs changes are not affected started checkout process test"/> + <description value="Changes in admin for shipping rates are not affecting checkout process that has been started"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12599"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + + <!-- Enable free shipping method --> + <magentoCLI command="config:set {{EnableFreeShippingConfigData.path}} {{EnableFreeShippingConfigData.value}}" stepKey="enableFreeShipping"/> + + <!-- Disable flat rate method --> + <magentoCLI command="config:set {{DisableFlatRateConfigData.path}} {{DisableFlatRateConfigData.value}}" stepKey="disableFlatRate"/> + </before> + <after> + <!-- Roll back configuration --> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{DisableFreeShippingConfigData.path}} {{DisableFreeShippingConfigData.value}}" stepKey="disableFreeShipping"/> + + <!-- Delete simple product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Add product to cart --> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Proceed to Checkout from mini shopping cart --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckout"/> + + <!-- Fill all required fields --> + <actionGroup ref="GuestCheckoutFillNewShippingAddressActionGroup" stepKey="fillNewShippingAddress"> + <argument name="customer" value="Simple_Customer_Without_Address" /> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <!-- Assert Free Shipping checkbox --> + <seeCheckboxIsChecked selector="{{CheckoutShippingMethodsSection.shippingMethodFreeShipping}}" stepKey="freeShippingMethodCheckboxIsChecked"/> + + <!-- Click Next button --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + + <!-- Payment step is opened --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + + <!-- Order Summary block contains information about shipping --> + <actionGroup ref="CheckShippingMethodInCheckoutActionGroup" stepKey="guestCheckoutCheckShippingMethod"> + <argument name="shippingMethod" value="freeTitleDefault.value"/> + </actionGroup> + + <!-- Open new browser's window and login as Admin --> + <openNewTab stepKey="openNewTab"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Go to Store > Configuration > Sales > Shipping Methods --> + <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage"/> + + <!-- Enable "Flat Rate" --> + <actionGroup ref="AdminChangeFlatRateShippingMethodStatusActionGroup" stepKey="enableFlatRateShippingStatus"/> + + <!-- Flush cache --> + <magentoCLI command="cache:flush" stepKey="cacheFlush"/> + + <!-- Back to the Checkout and refresh the page --> + <switchToPreviousTab stepKey="switchToPreviousTab"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitPageReload"/> + + <!-- Payment step is opened after refreshing --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSection"/> + + <!-- Order Summary block contains information about free shipping --> + <actionGroup ref="CheckShippingMethodInCheckoutActionGroup" stepKey="guestCheckoutCheckFreeShippingMethod"> + <argument name="shippingMethod" value="freeTitleDefault.value"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml index e19627e7435d6..9714b76a05613 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml @@ -15,7 +15,7 @@ <title value="Customer Checkout"/> <description value="To be sure that other elements of Success page are shown for placed order as registered Customer."/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-60345"/> + <testCaseId value="MC-16488"/> <group value="checkout"/> </annotations> @@ -135,10 +135,10 @@ <annotations> <features value="Checkout"/> <stories value="Success page elements are presented for placed order as Guest"/> - <title value="Customer Checkout"/> - <description value="To be sure that other elements of Success page are presented for placed order as Guest."/> + <title value="Guest Checkout - elements of success page are presented for placed order as guest"/> + <description value="To be sure that other elements of Success page are presented for placed order as Guest"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-60346"/> + <testCaseId value="MC-16490"/> <group value="checkout"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ConfiguringInstantPurchaseFunctionalityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ConfiguringInstantPurchaseFunctionalityTest.xml new file mode 100644 index 0000000000000..0897e20f1b17d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ConfiguringInstantPurchaseFunctionalityTest.xml @@ -0,0 +1,255 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ConfiguringInstantPurchaseFunctionalityTest"> + <annotations> + <features value="One Page Checkout"/> + <stories value="Configuring instant purchase"/> + <title value="Configuring instant purchase functionality test"/> + <description value="Configuring instant purchase"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-6436"/> + <group value="checkout"/> + <skip> + <issueId value="MQE-1576"/> + </skip> + </annotations> + <before> + <!-- Configure Braintree Vault-enabled payment method --> + <createData entity="BraintreeConfig" stepKey="braintreeConfigurationData"/> + <createData entity="CustomBraintreeConfigurationData" stepKey="enableBraintree"/> + + <!-- Create customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + + <!-- Create product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Set configs to default --> + <createData entity="DefaultBraintreeConfig" stepKey="defaultBraintreeConfig"/> + <createData entity="RollBackCustomBraintreeConfigurationData" stepKey="rollBackCustomBraintreeConfigurationData"/> + + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <!-- Admin logout --> + <actionGroup ref="logout" stepKey="adminLogout"/> + </after> + + <!-- Create store views --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createFirstStoreView"> + <argument name="customStore" value="storeViewData1"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="customStore" value="storeViewData2"/> + </actionGroup> + + <!-- Login to Frontend --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Add product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <!-- Customer placed order with payment method save --> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + + <!-- Fill Braintree cart data --> + <click selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="selectBraintreePaymentMethod"/> + <waitForPageLoad stepKey="waitForBraintreeFormLoad"/> + <scrollTo selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="scrollToCreditCardSection"/> + <actionGroup ref="StorefrontFillCartDataActionGroup" stepKey="fillCartData"/> + <waitForPageLoad stepKey="waitForFillCartData"/> + + <!-- Place order --> + <click selector="{{BraintreeConfigurationPaymentSection.paymentMethodContainer}}{{CheckoutPaymentSection.placeOrder}}" stepKey="checkoutPlaceOrder"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/> + + <!-- Go to Configuration > Sales --> + <actionGroup ref="AdminOpenInstantPurchaseConfigPageActionGroup" stepKey="openInstantPurchaseConfigPage"/> + + <!-- Enable Instant Purchase --> + <actionGroup ref="AdminChangeInstantPurchaseStatusActionGroup" stepKey="enableInstantPurchase"/> + + <!-- Switch to specific store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToSpecificStoreView"> + <argument name="storeView" value="storeViewData1.name"/> + </actionGroup> + + <!-- Change button text on a single store view --> + <actionGroup ref="AdminChangeInstantPurchaseButtonTextActionGroup" stepKey="changeInstantPurchaseButtonText"> + <argument name="buttonText" value="Quick Buy"/> + </actionGroup> + + <!-- Verify changes on the front-end by opening a simple product as a logged in customer with saved card and address on given store view: + 1. Go to Storefront page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefrontPage"/> + + <!-- 2. Switch store view --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="SwitchStoreView"> + <argument name="storeView" value="storeViewData1"/> + </actionGroup> + + <!-- 3. Assert customer is logged with saved card with address --> + <actionGroup ref="OpenStorefrontCustomerStoredPaymentMethodsPageActionGroup" stepKey="openStorefrontCustomerStoredPaymentMethodsPage"/> + <actionGroup ref="AssertStorefrontCustomerSavedCardActionGroup" stepKey="assertCustomerPaymentMethod"/> + <actionGroup ref="StorefrontCustomerGoToSidebarMenu" stepKey="goToAddressBook"> + <argument name="menu" value="Address Book"/> + </actionGroup> + <see selector="{{CheckoutOrderSummarySection.shippingAddress}}" userInput="{{US_Address_TX.street[0]}} {{US_Address_TX.city}}, {{US_Address_TX.state}}, {{US_Address_TX.postcode}}" stepKey="checkShippingAddress"/> + + <!-- Open product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Quick Buy button shows up. Clicking it opens review popup --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeQuickBuyButton"> + <argument name="selector" value="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}"/> + <argument name="userInput" value="Quick Buy"/> + </actionGroup> + <click selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="clickQuickBuyButton"/> + <waitForElementVisible selector="{{StorefrontInstantPurchasePopupSection.modalTitle}}" stepKey="waitForPopUpTitleVisible"/> + <see selector="{{StorefrontInstantPurchasePopupSection.modalTitle}}" userInput="Instant Purchase Confirmation" stepKey="seeReviewPopUp"/> + <click selector="{{StorefrontInstantPurchasePopupSection.cancel}}" stepKey="closeModalPopup"/> + <waitForPageLoad stepKey="waitForClosing"/> + + <!-- Verify changes on the front-end by opening a simple product as a logged in customer with saved card and address on a store view for which description was not changed + 1. New customer session should be started to verify changes --> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="customerLogout"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- 2. Switch store view which description was not changed --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="SwitchToSecondStoreView"> + <argument name="storeView" value="storeViewData2"/> + </actionGroup> + + <!-- 3. Assert customer is logged with saved card and address --> + <actionGroup ref="OpenStorefrontCustomerStoredPaymentMethodsPageActionGroup" stepKey="openStorefrontCustomerPaymentMethodsPage"/> + <actionGroup ref="AssertStorefrontCustomerSavedCardActionGroup" stepKey="assertPaymentMethod"/> + <actionGroup ref="StorefrontCustomerGoToSidebarMenu" stepKey="openAddressBook"> + <argument name="menu" value="Address Book"/> + </actionGroup> + <see selector="{{CheckoutOrderSummarySection.shippingAddress}}" userInput="{{US_Address_TX.street[0]}} {{US_Address_TX.city}}, {{US_Address_TX.state}}, {{US_Address_TX.postcode}}" stepKey="seeShippingAddress"/> + + <!-- Open product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openCreatedProductPage"> + <argument name="productUrl" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Instant Purchase button shows up. Clicking it opens review popup. --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeInstantPurchaseButton"> + <argument name="selector" value="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}"/> + <argument name="userInput" value="Instant Purchase"/> + </actionGroup> + <click selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="clickInstantPurchaseButton"/> + <waitForElementVisible selector="{{StorefrontInstantPurchasePopupSection.modalTitle}}" stepKey="waitForPopUpVisible"/> + <see selector="{{StorefrontInstantPurchasePopupSection.modalTitle}}" userInput="Instant Purchase Confirmation" stepKey="seeReviewPopUpTitle"/> + <click selector="{{StorefrontInstantPurchasePopupSection.cancel}}" stepKey="closeModalPopUp"/> + <waitForPageLoad stepKey="waitForModalClosing"/> + + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="createdCustomerLogout"/> + + <!-- Return to configuration and disable Instant Purchase in a specific store view --> + <actionGroup ref="AdminOpenInstantPurchaseConfigPageActionGroup" stepKey="openInstantPurchasePage"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToFirstSpecificStoreView"> + <argument name="storeView" value="storeViewData1.name"/> + </actionGroup> + <actionGroup ref="AdminChangeInstantPurchaseStatusActionGroup" stepKey="disableInstantPurchase"> + <argument name="status" value="0"/> + </actionGroup> + + <!-- Verify changes on the front-end by opening a simple product as a logged in customer with saved card and address in the specific store view + 1. New customer session should be started to verify changes --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCreatedCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- 2. Switch store view --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreView"> + <argument name="storeView" value="storeViewData1"/> + </actionGroup> + + <!-- 3. Assert customer is logged with saved card and address --> + <actionGroup ref="OpenStorefrontCustomerStoredPaymentMethodsPageActionGroup" stepKey="openCustomerPaymentMethodsPage"/> + <actionGroup ref="AssertStorefrontCustomerSavedCardActionGroup" stepKey="assertCartPaymentMethod"/> + <actionGroup ref="StorefrontCustomerGoToSidebarMenu" stepKey="goToAddressBookPage"> + <argument name="menu" value="Address Book"/> + </actionGroup> + <see selector="{{CheckoutOrderSummarySection.shippingAddress}}" userInput="{{US_Address_TX.street[0]}} {{US_Address_TX.city}}, {{US_Address_TX.state}}, {{US_Address_TX.postcode}}" stepKey="assertCustomerShippingAddress"/> + + <!-- Open product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductIndexPage"> + <argument name="productUrl" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Quick Buy button is not visible --> + <actionGroup ref="AssertStorefrontElementInvisibleActionGroup" stepKey="dontSeeQuickBuyButton"> + <argument name="selector" value="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}"/> + <argument name="userInput" value="Quick Buy"/> + </actionGroup> + + <!-- Verify changes on the front-end by opening a simple product as a logged in customer with saved card and address in the other store view + 1. New customer session should be started to verify changes --> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="logoutCustomer"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLoginToStorefront"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- 2. Switch store view --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchToSecondStoreView"> + <argument name="storeView" value="storeViewData2"/> + </actionGroup> + + <!-- 3. Assert customer is logged with saved card and address --> + <actionGroup ref="OpenStorefrontCustomerStoredPaymentMethodsPageActionGroup" stepKey="goToStorefrontCustomerPaymentMethodsPage"/> + <actionGroup ref="AssertStorefrontCustomerSavedCardActionGroup" stepKey="assertCardPaymentMethod"/> + <actionGroup ref="StorefrontCustomerGoToSidebarMenu" stepKey="openAddressBookPage"> + <argument name="menu" value="Address Book"/> + </actionGroup> + <see selector="{{CheckoutOrderSummarySection.shippingAddress}}" userInput="{{US_Address_TX.street[0]}} {{US_Address_TX.city}}, {{US_Address_TX.state}}, {{US_Address_TX.postcode}}" stepKey="seeCustomerShippingAddress"/> + + <!-- Open product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToProductPage"> + <argument name="productUrl" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Instant Purchase button is still visible --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeInstantPurchaseBtn"> + <argument name="selector" value="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}"/> + <argument name="userInput" value="Instant Purchase"/> + </actionGroup> + + <!-- Delete store views --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteFirstStoreView"> + <argument name="customStore" value="storeViewData1"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteSecondStoreView"> + <argument name="customStore" value="storeViewData2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..5ad4c764026f7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteBundleDynamicProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete bundle dynamic product from shopping cart test"/> + <description value="Delete bundle dynamic product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14689"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category and simple product --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create bundle product --> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleDynamicProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="createBundleDynamicProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createNewBundleLink"> + <requiredEntity createDataKey="createBundleDynamicProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete bundle product data --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createBundleDynamicProduct" stepKey="deleteBundleProduct"/> + </after> + + <!-- Go to bundle product page --> + <amOnPage url="{{StorefrontProductPage.url($$createBundleDynamicProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Add product to the cart --> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickCustomizeAndAddToCart"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProductToCart"> + <argument name="productName" value="$$createBundleDynamicProduct.name$$"/> + </actionGroup> + + <!-- Remove product from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> + <argument name="productName" value="$$createBundleDynamicProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..d1008c5831983 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteBundleFixedProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete bundle fixed product from shopping cart test"/> + <description value="Delete bundle fixed product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14690"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + + <!-- Create bundle product --> + <createData entity="ApiFixedBundleProduct" stepKey="createFixedBundleProduct"/> + <createData entity="DropDownBundleOption" stepKey="createBundleOption"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="addLinkOptionToBundleProduct"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + <requiredEntity createDataKey="createBundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!-- Delete bundle product data --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createFixedBundleProduct" stepKey="deleteFixedBundleProduct"/> + </after> + + <!-- Go to bundle product page --> + <amOnPage url="{{StorefrontProductPage.url($$createFixedBundleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Add product to the cart --> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickCustomizeAndAddToCart"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProductToCart"> + <argument name="productName" value="$$createFixedBundleProduct.name$$"/> + </actionGroup> + + <!-- Remove product from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> + <argument name="productName" value="$$createFixedBundleProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..891f647e3d5ef --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteConfigurableProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete configurable product from shopping cart test"/> + <description value="Delete configurable product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14692"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create configurable product --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct"/> + </createData> + </before> + <after> + <!-- Delete configurable product data --> + <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <!-- Add configurable product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption.value$$"/> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Remove product from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..e16ef70c23e3d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteDownloadableProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete downloadable product from shopping cart test"/> + <description value="Delete downloadable product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14693"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com"/> + <!-- Create downloadable product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + </before> + <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com"/> + <!-- Delete downloadable product --> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + </after> + + <!-- Add downloadable product to the cart --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartDownloadableProductFromStorefrontProductPage"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + + <!-- Remove product from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..eb8e753ea0b79 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteGroupedProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete grouped product from shopping cart test"/> + <description value="Delete grouped product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14694"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create grouped product with three simple products --> + <createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"/> + <createData entity="SimpleProduct2" stepKey="createSecondSimpleProduct"/> + <createData entity="SimpleProduct2" stepKey="createThirdSimpleProduct"/> + <createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/> + <createData entity="OneSimpleProductLink" stepKey="addFirstProductToLink"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createFirstSimpleProduct"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addFirstProductToLink" stepKey="addSecondProductTwo"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createSecondSimpleProduct"/> + </updateData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addFirstProductToLink" stepKey="addThirdProductThree"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createThirdSimpleProduct"/> + </updateData> + </before> + <after> + <!-- Delete grouped product data --> + <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createThirdSimpleProduct" stepKey="deleteThirdProduct"/> + <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> + </after> + + <!-- Add grouped product to the cart --> + <actionGroup ref="StorefrontAddThreeGroupedProductToTheCartActionGroup" stepKey="addGropedProductsToTheCart"> + <argument name="urlKey" value="$$createGroupedProduct.custom_attributes[url_key]$$"/> + <argument name="product1" value="$$createFirstSimpleProduct.name$$"/> + <argument name="product2" value="$$createSecondSimpleProduct.name$$"/> + <argument name="product3" value="$$createThirdSimpleProduct.name$$"/> + <argument name="qty1" value="1"/> + <argument name="qty2" value="1"/> + <argument name="qty3" value="1"/> + </actionGroup> + + <!-- Remove products from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <click selector="{{CheckoutCartProductSection.removeProductByName($$createFirstSimpleProduct.name$$)}}" stepKey="deleteFirstProductFromCheckoutCart"/> + <click selector="{{CheckoutCartProductSection.removeProductByName($$createSecondSimpleProduct.name$$)}}" stepKey="deleteSecondProductFromCheckoutCart"/> + <click selector="{{CheckoutCartProductSection.removeProductByName($$createThirdSimpleProduct.name$$)}}" stepKey="deleteThirdProductFromCheckoutCart"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml new file mode 100644 index 0000000000000..97dcdd52127fd --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeleteVirtualProductFromShoppingCartTest"> + <annotations> + <features value="Checkout"/> + <stories value="Delete Products from Shopping Cart"/> + <title value="Delete virtual product from shopping cart test"/> + <description value="Delete virtual product from shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14691"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create virtual product --> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <field key="price">50</field> + </createData> + </before> + <after> + <!-- Delete virtual product --> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + </after> + + <!-- Add virtual product to the cart --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartVirtualProductFromStorefrontProductPage"> + <argument name="productName" value="$$createVirtualProduct.name$$"/> + </actionGroup> + + <!-- Remove product from cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> + <argument name="productName" value="$$createVirtualProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index 5335ec2ad775d..a4864d612a45f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -9,6 +9,11 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> + <annotations> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> <!-- Step 3: User adds products to cart --> <comment userInput="Start of adding products to cart" stepKey="startOfAddingProductsToCart" after="endOfBrowsingCatalog"/> <!-- Add Simple Product 1 to cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 65627787e2a05..a4784a5cdc227 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -9,6 +9,11 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> + <annotations> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> <!-- Step 3: User adds products to cart --> <comment userInput="Start of adding products to cart" stepKey="startOfAddingProductsToCart" after="endOfBrowsingCatalog"/> <!-- Add Simple Product 1 to cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml new file mode 100644 index 0000000000000..aa3665a81bbde --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OnePageCheckoutAsCustomerUsingDefaultAddressTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout within Offline Payment Methods"/> + <title value="OnePageCheckout as customer using default address test"/> + <description value="Checkout as customer using default address"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14741"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create Simple Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="price">560</field> + </createData> + + <!-- Create customer --> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + </before> + <after> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Customer log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <!-- Add Simple Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Go to shopping cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + + <!-- Login as customer on checkout page --> + <actionGroup ref="LoginAsCustomerOnCheckoutPageActionGroup" stepKey="customerLogin"> + <argument name="customer" value="$createCustomer$"/> + </actionGroup> + + <!-- Fill customer address data --> + <waitForElementVisible selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="waitForShipHereVisible"/> + <!-- Change address --> + <click selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="clickShipHere"/> + + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + + <!-- Select payment solution --> + <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution" /> + + <!-- Check order summary in checkout --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open created order in backend --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrdersPageLoad"/> + <actionGroup ref="OpenOrderById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <!-- Assert order total --> + <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$565.00" stepKey="checkOrderTotalInBackend"/> + + <!-- Assert order addresses --> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="seeBillingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.city}}" stepKey="seeBillingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.postcode}}" stepKey="seeBillingAddressPostcode"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="seeShippingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.city}}" stepKey="seeShippingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.postcode}}" stepKey="seeShippingAddressPostcode"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml new file mode 100644 index 0000000000000..bafad6f28a680 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OnePageCheckoutAsCustomerUsingNewAddressTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout within Offline Payment Methods"/> + <title value="OnePageCheckout as customer using new address test"/> + <description value="Checkout as customer using new address"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14740"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create Simple Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="price">560</field> + </createData> + + <!-- Create customer --> + <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> + </before> + <after> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Customer log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <!-- Add Simple Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Go to shopping cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + <!-- Login using Sign In link from checkout page --> + <actionGroup ref="LoginAsCustomerUsingSignInLinkActionGroup" stepKey="customerLogin"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Add new address --> + <click selector="{{CheckoutShippingSection.newAddressButton}}" stepKey="addNewAddress"/> + + <!-- Fill in required fields and save --> + <actionGroup ref="FillShippingAddressOneStreetActionGroup" stepKey="changeAddress"> + <argument name="address" value="UK_Not_Default_Address"/> + </actionGroup> + <click selector="{{CheckoutShippingSection.saveAddress}}" stepKey="saveNewAddress"/> + <waitForPageLoad stepKey="waitForAddressSaving"/> + + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + + <!-- Change the address --> + <uncheckOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution"/> + <click selector="{{CheckoutPaymentSection.editAddress}}" stepKey="editAddress"/> + <waitForElementVisible selector="{{CheckoutShippingSection.addressDropdown}}" stepKey="waitForAddressDropDownToBeVisible"/> + <selectOption selector="{{CheckoutShippingSection.addressDropdown}}" userInput="New Address" stepKey="addAddress"/> + <waitForPageLoad stepKey="waitForNewAddressForm"/> + <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="changeBillingAddress"> + <argument name="Address" value="US_Address_NY"/> + </actionGroup> + <click selector="{{CheckoutShippingSection.updateAddress}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + + <!-- Place order --> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <waitForPageLoad stepKey="waitForCheckoutPaymentSectionPageLoad"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open created order in backend --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrdersPageLoad"/> + <actionGroup ref="OpenOrderById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <!-- Assert order total --> + <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$565.00" stepKey="checkOrderTotalInBackend"/> + + <!-- Assert order addresses --> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{US_Address_NY.street[0]}}" stepKey="seeBillingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{US_Address_NY.city}}" stepKey="seeBillingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{US_Address_NY.postcode}}" stepKey="seeBillingAddressPostcode"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="seeShippingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.city}}" stepKey="seeShippingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{UK_Not_Default_Address.postcode}}" stepKey="seeShippingAddressPostcode"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml new file mode 100644 index 0000000000000..2c341a5c4c1ab --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OnePageCheckoutAsCustomerUsingNonDefaultAddressTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout within Offline Payment Methods"/> + <title value="OnePageCheckout as customer using non default address test"/> + <description value="Checkout as customer using non default address"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14739"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create Simple Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="price">560</field> + </createData> + + <!-- Create customer --> + <createData entity="Customer_US_UK_DE" stepKey="createCustomer"/> + </before> + <after> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Customer Log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <!-- Add Simple Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Go to shopping cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + <!-- Login as customer on checkout page --> + <actionGroup ref="LoginAsCustomerOnCheckoutPageActionGroup" stepKey="customerLogin"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <click selector="{{CheckoutShippingSection.shipHereButton(DE_Address_Berlin_Not_Default_Address.street[0])}}" stepKey="clickShipHere"/> + + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + <uncheckOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution"/> + + <!-- Change the address --> + <click selector="{{CheckoutPaymentSection.editAddress}}" stepKey="editAddress"/> + <waitForElementVisible selector="{{CheckoutShippingSection.addressDropdown}}" stepKey="waitForDropDownToBeVisible"/> + <selectOption selector="{{CheckoutShippingSection.addressDropdown}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="addAddress"/> + + <!-- Check order summary in checkout --> + <click selector="{{CheckoutShippingSection.updateAddress}}" stepKey="clickToUpdate"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Place order --> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <waitForPageLoad stepKey="waitForCheckoutPaymentSectionPageLoad"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open created order in backend --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrdersPageLoad"/> + <actionGroup ref="OpenOrderById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <!-- Assert order total --> + <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$565.00" stepKey="checkOrderTotalInBackend"/> + + <!-- Assert order addresses --> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="seeBillingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.city}}" stepKey="seeBillingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{UK_Not_Default_Address.postcode}}" stepKey="seeBillingAddressPostcode"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{DE_Address_Berlin_Not_Default_Address.street[0]}}" stepKey="seeShippingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{DE_Address_Berlin_Not_Default_Address.city}}" stepKey="seeShippingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{DE_Address_Berlin_Not_Default_Address.postcode}}" stepKey="seeShippingAddressPostcode"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml new file mode 100644 index 0000000000000..990459d7c81b7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutUsingSignInLinkTest.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OnePageCheckoutUsingSignInLinkTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout within Offline Payment Methods"/> + <title value="OnePageCheckout using sign in link test"/> + <description value="Checkout using 'Sign In' link"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14738"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create Simple Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="price">560</field> + </createData> + + <!-- Create customer --> + <createData entity="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" stepKey="createCustomer"/> + </before> + <after> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Customer Log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + + <!-- Add Simple Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Go to shopping cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + <!-- Login using Sign In link from checkout page --> + <actionGroup ref="LoginAsCustomerUsingSignInLinkActionGroup" stepKey="customerLogin"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + + <!-- Select payment solution --> + <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution" /> + + <!-- Check order summary in checkout --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open created order in backend --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrdersPageLoad"/> + <actionGroup ref="OpenOrderById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <!-- Assert that shipping and billing address are the same --> + <grabTextFrom selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" stepKey="shippingAddress"/> + <grabTextFrom selector="{{AdminShipmentAddressInformationSection.billingAddress}}" stepKey="billingAddress"/> + <assertEquals stepKey="assertAddress" actual="$billingAddress" expected="$shippingAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml new file mode 100644 index 0000000000000..e85a47ab7a91d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OnePageCheckoutWithAllProductTypesTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout within Offline Payment Methods"/> + <title value="OnePageCheckout with all product types test"/> + <description value="Checkout with all product types"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14742"/> + <group value="checkout"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com"/> + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Configurable Product --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct"/> + </createData> + + <!--Create Bundle Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProductForBundleProduct"/> + <createData entity="ApiFixedBundleProduct" stepKey="createFixedBundleProduct"/> + <createData entity="DropDownBundleOption" stepKey="createBundleOption"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="firstLinkOptionToFixedProduct"> + <requiredEntity createDataKey="createFixedBundleProduct"/> + <requiredEntity createDataKey="createBundleOption"/> + <requiredEntity createDataKey="createSimpleProductForBundleProduct"/> + </createData> + + <!-- Create Virtual Product --> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"/> + + <!--Create Downloadable Product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + + <!-- Create Grouped Product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + + <!-- Create customer --> + <createData entity="Simple_Customer_Without_Address" stepKey="createCustomer"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com"/> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete all created products --> + <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createSimpleProductForBundleProduct" stepKey="deleteSimpleProductForBundleProduct"/> + <deleteData createDataKey="createFixedBundleProduct" stepKey="deleteFixedBundleProduct"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <!-- Logout customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> + </after> + + <!-- Add Simple Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Add Configurable Product to cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption.value$$"/> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Add Virtual Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToVirtualProductPage"/> + <waitForPageLoad stepKey="waitForVirtualProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartVirtualProductFromStorefrontProductPage"> + <argument name="productName" value="$$createVirtualProduct.name$$"/> + </actionGroup> + + <!-- Add Downloadable Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartDownloadableProductFromStorefrontProductPage"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + + <!-- Add Grouped Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createGroupedProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToGroupedProductPage"/> + <waitForPageLoad stepKey="waitForGroupedProductPageLoad"/> + <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="1" stepKey="fillFieldQtyInput"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartGroupedProductFromStorefrontProductPage"> + <argument name="productName" value="$$createGroupedProduct.name$$"/> + </actionGroup> + + <!-- Add Bundle Product to cart --> + <amOnPage url="{{StorefrontProductPage.url($$createFixedBundleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToBundleProductPage"/> + <waitForPageLoad stepKey="waitForFixedBundleProductPageLoad"/> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickCustomizeAndAddToCart"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFixedBundleProductFromStorefrontProductPage"> + <argument name="productName" value="$$createFixedBundleProduct.name$$"/> + </actionGroup> + + <!--Go to shopping cart--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <actionGroup ref="FillShippingZipForm" stepKey="fillShippingZipForm"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <actionGroup ref="FillCustomerSignInPopupFormActionGroup" stepKey="fillCustomerSignInPopupForm"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <amOnPage url="{{CheckoutShippingPage.url}}" stepKey="navigateToShippingPage"/> + <waitForPageLoad stepKey="waitForShippingPageLoad"/> + + <!-- Fill customer address data --> + <fillField selector="{{CheckoutShippingGuestInfoSection.company}}" userInput="{{CustomerAddressSimple.company}}" stepKey="fillCompany"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{CustomerAddressSimple.street}}" stepKey="fillStreet"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="fillCity" /> + <selectOption selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="fillZipCode" /> + <fillField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="fillPhone" /> + + <!-- Click next button to open payment section --> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNextBtn"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + + <!-- Check order summary in checkout --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open created order --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrderPageLoad"/> + <actionGroup ref="OpenOrderById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <!-- Assert that addresses on order page the same --> + <grabTextFrom selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" stepKey="shippingAddressOrderPage"/> + <grabTextFrom selector="{{AdminShipmentAddressInformationSection.billingAddress}}" stepKey="billingAddressOrderPage"/> + <assertEquals actual="$billingAddressOrderPage" expected="$shippingAddressOrderPage" stepKey="assertAddressOrderPage"/> + + <!-- Assert order total --> + <amOnPage url="{{StorefrontCustomerDashboardPage.url}}" stepKey="navigateToCustomerDashboardPage"/> + <waitForPageLoad stepKey="waitForCustomerDashboardPageLoad"/> + <see selector="{{StorefrontCustomerRecentOrdersSection.orderTotal}}" userInput="$613.23" stepKey="checkOrderTotalInStorefront"/> + + <!-- Go to Address Book --> + <actionGroup ref="StorefrontCustomerGoToSidebarMenu" stepKey="goToAddressBook"> + <argument name="menu" value="Address Book"/> + </actionGroup> + + <!-- Asserts that addresses in address book equal to addresses in order --> + <grabTextFrom selector="{{CheckoutOrderSummarySection.shippingAddress}}" stepKey="shippingAddress"/> + <grabTextFrom selector="{{CheckoutOrderSummarySection.billingAddress}}" stepKey="billingAddress"/> + <assertEquals actual="$shippingAddress" expected="$shippingAddressOrderPage" stepKey="assertShippingAddress"/> + <assertEquals actual="$billingAddress" expected="$billingAddressOrderPage" stepKey="assertBillingAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml new file mode 100644 index 0000000000000..84cdb8abd9344 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ShoppingCartAndMiniShoppingCartPerCustomerTest"> + <annotations> + <features value="Checkout"/> + <stories value="Shopping Cart and Mini Shopping Cart per Customer"/> + <title value="Shopping cart and mini shopping cart per customer test"/> + <description value="Shopping cart and mini shopping cart per customer with enabled cached"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14730"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Flush cache --> + <magentoCLI command="cache:flush" stepKey="clearCache"/> + + <!-- Create two customers --> + <createData entity="Simple_US_Customer" stepKey="createFirstCustomer"/> + <createData entity="Simple_US_CA_Customer" stepKey="createSecondCustomer"/> + + <!-- Create products --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create simple product --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create simple product with custom options --> + <createData entity="_defaultProduct" stepKey="createSimpleProductWithCustomOptions"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createSimpleProductWithCustomOptions" entity="productWithDropdownAndFieldOptions" stepKey="updateProductWithCustomOption"/> + </before> + <after> + <!-- Delete products --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createSimpleProductWithCustomOptions" stepKey="deleteSimpleProductWithCustomOptions"/> + + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete customers --> + <deleteData createDataKey="createFirstCustomer" stepKey="deleteFirstCustomer"/> + <deleteData createDataKey="createSecondCustomer" stepKey="deleteSecondCustomer"/> + </after> + + <!-- Login as first customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccountAsFirstCustomer"> + <argument name="Customer" value="$$createFirstCustomer$$"/> + </actionGroup> + + <!-- Assert cart is empty --> + <actionGroup ref="AssertShoppingCartIsEmptyActionGroup" stepKey="seeEmptyShoppingCartForFirstCustomer"/> + + <!-- Go to first product page --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="goToFirstProductPage"/> + <waitForPageLoad stepKey="waitForFirstProductPageLoad"/> + + <!-- Add the product to the shopping cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addFirstProductToCart"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!-- Go to the second product page --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProductWithCustomOptions.custom_attributes[url_key]$$)}}" stepKey="goToSecondProductPage"/> + <waitForPageLoad stepKey="waitForSecondProductPageLoad"/> + + <!-- Fill the custom options values --> + <actionGroup ref="StorefrontSelectOptionDropDownActionGroup" stepKey="selectFirstOption"> + <argument name="optionTitle" value="ProductOptionValueDropdown"/> + <argument name="option" value="ProductOptionValueWithSkuDropdown1.title"/> + </actionGroup> + <fillField selector="{{StorefrontProductInfoMainSection.productOptionFieldInput(ProductOptionField.title)}}" userInput="OptionField" stepKey="fillProductOptionInputField"/> + + <!-- Add the product to the shopping cart --> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addSecondProductToCart"> + <argument name="productName" value="$$createSimpleProductWithCustomOptions.name$$"/> + </actionGroup> + + <!-- Logout first customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="firstCustomerLogout"/> + + <!-- Login as second customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccountAsSecondCustomer"> + <argument name="Customer" value="$$createSecondCustomer$$"/> + </actionGroup> + + <!-- Assert cart is empty --> + <actionGroup ref="AssertShoppingCartIsEmptyActionGroup" stepKey="seeEmptyShoppingCartForSecondCustomer"/> + + <!-- Go to first product page --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!-- Add the product to the shopping cart --> + <actionGroup ref="AddProductWithQtyToCartFromStorefrontProductPage" stepKey="addProductToCart"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + <argument name="productQty" value="{{quoteQty2Price123.qty}}"/> + </actionGroup> + + <!-- Logout second customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="secondCustomerLogout"/> + + <!-- Login as first customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsFirstCustomer"> + <argument name="Customer" value="$$createFirstCustomer$$"/> + </actionGroup> + + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + + <!-- Assert first products present in shopping cart --> + <actionGroup ref="StorefrontCheckCartSimpleProductActionGroup" stepKey="checkFirstProductInCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="productQuantity" value="ApiSimpleSingleQty.quantity"/> + </actionGroup> + + <!-- Assert second products present in shopping cart --> + <seeElement selector="{{CheckoutCartProductSection.ProductLinkByName($$createSimpleProductWithCustomOptions.name$$)}}" stepKey="assertProductName"/> + <see selector="{{CheckoutCartProductSection.ProductPriceByName($$createSimpleProductWithCustomOptions.name$$)}}" userInput="{{quoteQty2Subtotal266.currency}}{{quoteQty2Subtotal266.price}}" stepKey="assertProductPrice"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute($$createSimpleProductWithCustomOptions.name$$, ProductOptionField.title)}}" userInput="OptionField" stepKey="seeFieldOption"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute($$createSimpleProductWithCustomOptions.name$$, ProductOptionValueDropdown.title)}}" userInput="{{ProductOptionValueWithSkuDropdown1.title}}" stepKey="seeDropDownOption"/> + + <!-- Assert subtotal and grand total --> + <see selector="{{StorefrontProductPageSection.subTotal}}" userInput="{{quoteQty2Subtotal266.currency}}{{quoteQty2Subtotal266.subtotal}}" stepKey="seeFirstCustomerSubTotal"/> + <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="{{quoteQty2Subtotal266.currency}}{{quoteQty2Subtotal266.total}}" stepKey="seeFirstCustomerOrderTotal"/> + + <!-- Assert products in mini cart for first customer --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> + <waitForPageLoad stepKey="waitForHomePageLoad"/> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="assertFirstProductInMiniCart"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="assertSecondProductInMiniCart"> + <argument name="productName" value="$$createSimpleProductWithCustomOptions.name$$"/> + </actionGroup> + <actionGroup ref="AssertMiniShoppingCartSubTotalActionGroup" stepKey="assertMiniCartSubTotal"> + <argument name="dataQuote" value="quoteQty2Subtotal266"/> + </actionGroup> + + <!-- Logout first customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutFirstCustomer"/> + + <!-- Login as second customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsSecondCustomer"> + <argument name="Customer" value="$$createSecondCustomer$$"/> + </actionGroup> + + <!-- Assert first products present in shopping cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCheckCartSimpleProductActionGroup" stepKey="checkProductInCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="productQuantity" value="quoteQty2Price123.qty"/> + </actionGroup> + + <!-- Assert subtotal and grand total --> + <see selector="{{StorefrontProductPageSection.subTotal}}" userInput="{{quoteQty2Price123.currency}}{{quoteQty2Price123.subtotal}}" stepKey="seeSecondCustomerSubTotal"/> + <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="{{quoteQty2Price123.currency}}{{quoteQty2Price123.total}}" stepKey="seeSecondCustomerOrderTotal"/> + + <!-- Assert product in mini cart --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageToLoad"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertProductInMiniCart"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + <argument name="productPrice" value="$$createSimpleProduct.price$$"/> + <argument name="cartSubtotal" value="{{quoteQty2Price123.currency}}{{quoteQty2Price123.subtotal}}"/> + <argument name="qty" value="{{quoteQty2Price123.qty}}"/> + </actionGroup> + + <!-- Logout second customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutSecondCustomer"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml new file mode 100644 index 0000000000000..d108dc3657a40 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add simple product with all types of custom options to cart without selecting any options"/> + <description value="Add simple product with all types of custom options to cart without selecting any options"/> + <testCaseId value="MC-14725"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createProduct" entity="productWithOptions" stepKey="updateProductWithCustomOptions"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!--Click on Add To Cart button--> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Assert all types of product options field displayed Required message --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionField"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionField.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionDropDown"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionDropDown.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionRadioButton"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionRadiobutton.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionCheckBox"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionCheckbox.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionMultiSelect"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionMultiSelect.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductFileOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomFile(ProductOptionFile.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductDateOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionDate.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductDateAndTimeOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionDateTime.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProducTimeOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionTime.title)}}"/> + </actionGroup> + + <!-- Verify mini cart is empty --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertEmptryCart"> + <argument name="selector" value="{{StorefrontMiniCartSection.emptyMiniCart}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..f63cf38d15fce --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddBundleDynamicProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add bundle dynamic product to the cart"/> + <description value="Add bundle dynamic product to the cart"/> + <testCaseId value="MC-14715"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + + <!--Assert Product Price Range --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $50.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Product, Quantity and add to the cart --> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection($$simpleProduct2.name$$)}}" stepKey="selectProduct"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="2"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="10.00"/> + <argument name="total" value="110.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert Product Option In CheckOut Cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitleInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml new file mode 100644 index 0000000000000..dcffd5cee24e4 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create Grouped product and verify mini cart action with disabled and enable Mini Cart Sidebar"/> + <description value="Create Grouped product and verify mini cart action with disabled and enable Mini Cart Sidebar"/> + <testCaseId value="MC-14719"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI stepKey="disableShoppingCartSidebar" command="config:set checkout/sidebar/display 0"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRatePrice"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + + <!--Create simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + + <!--Create Bundle product--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + <magentoCLI stepKey="disableShoppingCartSidebar" command="config:set checkout/sidebar/display 1"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $50.00"/> + </actionGroup> + + <!-- Add Bundle dynamic product to the cart --> + <actionGroup ref="StorefrontAddBundleProductToTheCartActionGroup" stepKey="addBundleDynamicProductToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="quantity" value="2"/> + </actionGroup> + + <!-- Select Mini Cart, verify it doen't open --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <dontSeeElement selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="dontSeeViewAndEditCart"/> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert Grouped product with option is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="10.00"/> + <argument name="total" value="110.00"/> + </actionGroup> + + <!--Enabled Shopping Cart Sidebar --> + <magentoCLI stepKey="enableShoppingCartSidebar" command="config:set checkout/sidebar/display 1"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <reloadPage stepKey="reloadThePage"/> + + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart1"/> + + <!--Assert mini cart can open and check mini cart items --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProductInMiniCart1"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..90d7643a1269f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddConfigurableProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create configurable product with three options and add configurable product to the cart"/> + <description value="Create configurable product with three options and add configurable product to the cart"/> + <testCaseId value="MC-14716"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRatePrice"/> + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with three options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the third option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">10.00</field> + </createData> + + <!--Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <field key="price">20.00</field> + </createData> + + <!--Create a simple product and give it the attribute with the Third option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + <field key="price">30.00</field> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductThreeOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- Add the third simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild3"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct3"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createConfigChildProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> + </after> + + <!-- Add Configurable Product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption2.label$$"/> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary --> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$40.00"/> + <argument name="shipping" value="10.00"/> + <argument name="total" value="50.00"/> + </actionGroup> + + <!--Assert Product Details In Checkout cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="productSku" value="$$createConfigChildProduct2.sku$$"/> + <argument name="productPrice" value="$$createConfigChildProduct2.price$$"/> + <argument name="subtotal" value="$40.00" /> + <argument name="qty" value="2"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + + <!-- Assert product details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertMiniCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="productPrice" value="$$createConfigChildProduct2.price$$"/> + <argument name="cartSubtotal" value="$40.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..ec9852a6a939d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddDownloadableProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create Downloadable product with two links and add to the shopping cart"/> + <description value="Create Downloadable product with two links and add to the shopping cart"/> + <testCaseId value="MC-14717"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="downloadableLink2" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Downloadable Product page --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Add Downloadable product to the cart --> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$" /> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryItemsActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$123.00"/> + <argument name="total" value="123.00"/> + </actionGroup> + + <!--Assert Product Details In Checkout cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + <argument name="productSku" value="$$createDownloadableProduct.sku$$"/> + <argument name="productPrice" value="$123.00"/> + <argument name="subtotal" value="$123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeLinksInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="Links"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionLink1"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="{{downloadableLink1.title}}"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionLink2"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="{{downloadableLink2.title}}"/> + </actionGroup> + + <!-- Assert product details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertMiniCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + <argument name="productPrice" value="$123.00"/> + <argument name="cartSubtotal" value="123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..247fab27143b8 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddGroupedProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create grouped product with three simple product and Add grouped product to the cart"/> + <description value="Create grouped product with three simple product and Add grouped product to the cart"/> + <testCaseId value="MC-14718"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <!--Create Grouped product with three simple product --> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"> + <field key="price">100.00</field> + </createData> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="simple3"> + <field key="price">200.00</field> + </createData> + <createData entity="ApiProductWithDescription" stepKey="simple3" before="product"> + <field key="price">300.00</field> + </createData> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductThree"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple3"/> + </updateData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simple2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simple3" stepKey="deleteProduct3"/> + <deleteData createDataKey="product" stepKey="deleteGroupProduct"/> + </after> + + <!-- Fill Quantity and add Product to the cart --> + <actionGroup ref="StorefrontAddThreeGroupedProductToTheCartActionGroup" stepKey="addGropedProductsToTheCart"> + <argument name="urlKey" value="$$product.custom_attributes[url_key]$$"/> + <argument name="product1" value="$$simple1.name$$"/> + <argument name="product2" value="$$simple2.name$$"/> + <argument name="product3" value="$$simple3.name$$"/> + <argument name="qty1" value="1"/> + <argument name="qty2" value="2"/> + <argument name="qty3" value="3"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Product1 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$simple1.name$$"/> + <argument name="productSku" value="$$simple1.sku$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Assert Product2 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct2ItemsInCheckOutCart"> + <argument name="productName" value="$$simple2.name$$"/> + <argument name="productSku" value="$$simple2.sku$$"/> + <argument name="productPrice" value="$200.00"/> + <argument name="subtotal" value="$400.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!--Assert Product3 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct3ItemsInCheckOutCart"> + <argument name="productName" value="$$simple3.name$$"/> + <argument name="productSku" value="$$simple3.sku$$"/> + <argument name="productPrice" value="$300.00"/> + <argument name="subtotal" value="$900.00" /> + <argument name="qty" value="3"/> + </actionGroup> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$1,400.00"/> + <argument name="shipping" value="30.00"/> + <argument name="total" value="1,430.00"/> + </actionGroup> + + <!-- Assert product1 details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simple3.name$$"/> + <argument name="productPrice" value="$300.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="3"/> + </actionGroup> + + <!-- Assert product2 details in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simple2.name$$"/> + <argument name="productPrice" value="$200.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert product3 details in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct1MiniCart"> + <argument name="productName" value="$$simple1.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..73fa380f4bc40 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Select one multi select option of a bundle product and add to the shopping cart"/> + <description value="Select one multi select option of a bundle product and add to the shopping cart "/> + <testCaseId value="MC-14727"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product with Multi Select option--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront and assert product details --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $60.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select One Product Option from MultiSelect option --> + <selectOption selector="{{StorefrontBundledSection.multiSelectOption}}" userInput="$$simpleProduct2.name$$ +$50.00" stepKey="selectOption2Product"/> + + <!--Enter product Quantity and add to the cart --> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$50.00"/> + <argument name="shipping" value="5.00"/> + <argument name="total" value="55.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$50.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Grouped product options is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProductInMiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$50.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..e90f69e88cec7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create a simple products with all types of oprtions and add it to the cart"/> + <description value="Create a simple products with all types of oprtions and add it to the cart"/> + <testCaseId value="MC-14726"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createProduct" entity="productWithOptions" stepKey="updateProductWithCustomOptions"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + <!-- Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Fill Option Input field --> + <actionGroup ref="StorefrontFillOptionFieldInputActionGroup" stepKey="fillTheOptionFieldInput"> + <argument name="fieldInput" value="1"/> + </actionGroup> + + <!-- Fill Option Text Area --> + <actionGroup ref="StorefrontFillOptionTextAreaActionGroup" stepKey="fillTheOptionTextAreaInput"> + <argument name="fieldInput" value="1"/> + </actionGroup> + + <!-- Attach file option--> + <actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachFile"/> + + <!--Select Option From DropDown option --> + <actionGroup ref="StorefrontSelectOptionDropDownActionGroup" stepKey="selectDropDownOption"/> + <scrollTo selector="{{StorefrontProductInfoMainSection.customOptionLabel(ProductOptionMultiSelect.title)}}" stepKey="scrollToMultiSelect"/> + + <!-- Select CheckBox From CheckBox option --> + <actionGroup ref="StorefrontSelectOptionCheckBoxActionGroup" stepKey="selectCheckBoxOption"/> + + <!-- Select RadioButton From Radio Button option --> + <actionGroup ref="StorefrontSelectOptionRadioButtonActionGroup" stepKey="selectRadioButtonOption"/> + + <!-- Select option From MultiSelect option --> + <actionGroup ref="StorefrontSelectOptionMultiSelectActionGroup" stepKey="selectOptionFromMultiSelect"/> + + <!-- Generate Date --> + <generateDate date="Now" format="Y" stepKey="year"/> + + <!-- Select Month,Day and Year From Date option --> + <actionGroup ref="StorefrontSelectOptionDateActionGroup" stepKey="fillOptionDate"> + <argument name="month" value="11"/> + <argument name="day" value="10"/> + <argument name="year" value="$year"/> + </actionGroup> + + <!-- Select Month, Day, Year, Hour,Minute and DayPart from DateTime option --> + <actionGroup ref="StorefrontSelectOptionDateTimeActionGroup" stepKey="fillOptionDateAndTime"> + <argument name="month" value="11"/> + <argument name="day" value="10"/> + <argument name="year" value="$year"/> + <argument name="hour" value="10"/> + <argument name="minute" value="20"/> + <argument name="dayPart" value="AM"/> + </actionGroup> + + <!-- Select Hour,Minute and DayPart from Time option --> + <actionGroup ref="StorefrontSelectOptionTimeActionGroup" stepKey="fillOptionTime"> + <argument name="hour" value="10"/> + <argument name="minute" value="20"/> + <argument name="dayPart" value="AM"/> + </actionGroup> + + <!-- Add product to the cart --> + <fillField selector="{{StorefrontProductInfoMainSection.qty}}" userInput="2" stepKey="fillQuantity"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToTheCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="2"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="2"/> + <argument name="productCountText" value="2 Item in Cart"/> + </actionGroup> + + <!--Assert Product Items in Mini cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createProduct.name$$"/> + <argument name="productPrice" value="$1,642.58"/> + <argument name="cartSubtotal" value="$3,285.16" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..e2ff7f2c98dca --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add two products to the cart from multi select options of a bundle product"/> + <description value="Add two products to the cart from multi select options of a bundle product."/> + <testCaseId value="MC-14728"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product with multi select option--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $60.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Two Products, enter the quantity and add product to the cart --> + <selectOption selector="{{StorefrontBundledSection.multiSelectOption}}" parameterArray="[$$simpleProduct1.name$$ +$10.00, $$simpleProduct2.name$$ +$50.00]" stepKey="selectOptions"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$60.00"/> + <argument name="shipping" value="5.00"/> + <argument name="total" value="65.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="subtotal" value="$60.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Grouped product options is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOption1InCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct1.name$$ $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOption2InCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$60.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml new file mode 100644 index 0000000000000..bdfdfceab53f9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontApplyPromoCodeDuringCheckoutTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="OnePageCheckout with Promo Code"/> + <title value="Storefront apply promo code during checkout test"/> + <description value="Apply promo code during checkout for physical product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13212"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createProduct"> + <field key="price">10.00</field> + </createData> + + <!-- Create cart price rule --> + <createData entity="ActiveSalesRuleForNotLoggedIn" stepKey="createCartPriceRule"/> + <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> + <requiredEntity createDataKey="createCartPriceRule"/> + </createData> + </before> + <after> + <!-- Delete simple product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Delete sales rule --> + <deleteData createDataKey="createCartPriceRule" stepKey="deleteCartPriceRule"/> + + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to Storefront as Guest and add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Go to Checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <!-- Fill all required fields with valid data and select Flat Rate, price = 5, shipping --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + + <!-- Click Apply Discount Code: section is expanded. Input promo code, apply and see success message --> + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyCoupon"> + <argument name="discountCode" value="$$createCouponForCartPriceRule.code$$"/> + </actionGroup> + + <!-- Apply button is disappeared --> + <dontSeeElement selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="dontSeeApplyButton"/> + + <!-- Cancel coupon button is appeared --> + <seeElement selector="{{DiscountSection.CancelCouponBtn}}" stepKey="seeCancelCouponButton"/> + + <!-- Order summary contains information about applied code --> + <seeElement selector="{{CheckoutPaymentSection.discount}}" stepKey="seeDiscountCouponInSummaryBlock"/> + <see selector="{{CheckoutPaymentSection.discountPrice}}" userInput="-$5.00" stepKey="seeDiscountPrice"/> + + <!-- Select payment solution --> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="clickCheckMoneyOrderPayment"/> + + <!-- Place Order: order is successfully placed --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Verify total on order page --> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForAdminOrderPageLoad"/> + <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$$createProduct.price$$" stepKey="checkTotal"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml new file mode 100644 index 0000000000000..0327deaf18968 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCartItemsCountDisplayItemsQuantities"> + <annotations> + <stories value="Checkout order summary has wrong item count"/> + <title value="Checkout order summary has wrong item count - display items quantities"/> + <description value="Items count in shopping cart and on checkout page should be consistent with settings 'checkout/cart_link/use_qty'"/> + <testCaseId value="MC-18281"/> + <severity value="CRITICAL"/> + <group value="checkout"/> + </annotations> + + <before> + <!--Set Display Cart Summary to display items quantities--> + <magentoCLI command="config:set {{DisplayItemsQuantities.path}} {{DisplayItemsQuantities.value}}" stepKey="setDisplayCartSummary"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <magentoCLI command="config:set {{DisplayItemsQuantities.path}} {{DisplayItemsQuantities.value}}" stepKey="resetDisplayCartSummary"/> + </after> + + <!-- Add simpleProduct1 to cart --> + <amOnPage url="{{StorefrontProductPage.url($$simpleProduct1.custom_attributes[url_key]$)}}" stepKey="amOnProduct1Page"/> + <actionGroup ref="AddProductWithQtyToCartFromStorefrontProductPage" stepKey="addProduct1ToCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productQty" value="2"/> + </actionGroup> + <!-- Add simpleProduct2 to cart --> + <amOnPage url="{{StorefrontProductPage.url($$simpleProduct2.custom_attributes[url_key]$)}}" stepKey="amOnProduct2Page"/> + <actionGroup ref="AddProductWithQtyToCartFromStorefrontProductPage" stepKey="addProduct2ToCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Products Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="3"/> + <argument name="productCountText" value="3 Items in Cart"/> + </actionGroup> + <!-- Assert Products Count on checkout page --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="assertProductCountOnCheckoutPage"> + <argument name="itemsText" value="3 Items in Cart"/> + </actionGroup> + </test> + <test name="StorefrontCartItemsCountDisplayUniqueItems" extends="StorefrontCartItemsCountDisplayItemsQuantities"> + <annotations> + <stories value="Checkout order summary has wrong item count"/> + <title value="Checkout order summary has wrong item count - display unique items"/> + <description value="Items count in shopping cart and on checkout page should be consistent with settings 'checkout/cart_link/use_qty'"/> + <testCaseId value="MC-18281"/> + <severity value="CRITICAL"/> + <group value="checkout"/> + </annotations> + + <!-- Assert Products Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="2"/> + <argument name="productCountText" value="2 Items in Cart"/> + </actionGroup> + <!-- Assert Products Count on checkout page --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="assertProductCountOnCheckoutPage"> + <argument name="itemsText" value="2 Items in Cart"/> + </actionGroup> + + <before> + <!--Set Display Cart Summary to display items quantities--> + <magentoCLI command="config:set {{DisplayUniqueItems.path}} {{DisplayUniqueItems.value}}" stepKey="setDisplayCartSummary"/> + </before> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml new file mode 100644 index 0000000000000..beb2d40f94cad --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml @@ -0,0 +1,343 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 items to the cart and check cart and summary block display with default display limit"/> + <description value="Add 10 items to the cart and check cart and summary block display with default display limit"/> + <testCaseId value="MC-14722"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="10"/> + <argument name="productCountText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertOrderSummary"> + <argument name="itemsText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Shipping Page --> + <actionGroup ref="StorefrontAssertShippingAddressPageDisplayActionGroup" stepKey="assertShippingPageDisplay"/> + + <!--Click on order summary tab --> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Product1 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct1InOrderSummary"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$10.00"/> + </actionGroup> + + <!-- Assert Product2 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct2InOrderSummary"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$20.00"/> + </actionGroup> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + + <!-- Assert Product5 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct5InOrderSummary"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$50.00"/> + </actionGroup> + + <!-- Assert Product6 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct6InOrderSummary"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$60.00"/> + </actionGroup> + + <!-- Assert Product7 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct7InOrderSummary"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$70.00"/> + </actionGroup> + + <!-- Assert Product8 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct8InOrderSummary"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$80.00"/> + </actionGroup> + + <!-- Assert Product9 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct9InOrderSummary"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$90.00"/> + </actionGroup> + + <!-- Assert Product10 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct10InOrderSummary"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$100.00"/> + </actionGroup> + + <!-- Verify View and Edit Cart is not displayed in Order Summary --> + <dontSeeElement selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="dontSeeViewAndEditLink"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml new file mode 100644 index 0000000000000..09a5ce4c70373 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml @@ -0,0 +1,286 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 11 Simple product to the cart and check cart display with default display limit"/> + <description value="Add 11 Simple product to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14720"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct11"> + <field key="price">110.00</field> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + <deleteData createDataKey="simpleProduct11" stepKey="deleteProduct11"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Product11 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage11AndVerifyProduct"> + <argument name="product" value="$$simpleProduct11$$"/> + </actionGroup> + + <!-- Add Product11 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct11ToTheCart"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + </actionGroup> + + + <!-- Assert Product details in Mini Cart --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductCountInMiniCart"> + <argument name="selector" value="{{StorefrontMinicartSection.productCount}}"/> + <argument name="userInput" value="11"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeTotalNumberOfItemDisplayed"> + <argument name="selector" value="{{StorefrontMinicartSection.visibleItemsCountText}}"/> + <argument name="userInput" value="10 of 11 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLink"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct1InMiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2InMiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4InMiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5InMiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6InMiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7InMiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8InMiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9InMiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10InMiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Verify Product11 is not displayed in Mini Cart --> + <dontSee selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="$$simpleProduct11.name$$" stepKey="dontSeeProduct11NameInMiniCart"/> + <dontSee selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="110" stepKey="dontSeeProduct11PriceInMiniCart"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml new file mode 100644 index 0000000000000..2339789bd85d1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml @@ -0,0 +1,256 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 items to the cart and check cart display with default display limit"/> + <description value="Add 10 items to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14721"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="10"/> + <argument name="productCountText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml new file mode 100644 index 0000000000000..084c89312cc7e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml @@ -0,0 +1,357 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 SimpleProducts to the cart and check cart display with default display limit"/> + <description value="Add 10 SimpleProducts to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14723"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct11"> + <field key="price">110.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + <deleteData createDataKey="simpleProduct11" stepKey="deleteProduct11"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Product11 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage11AndVerifyProduct"> + <argument name="product" value="$$simpleProduct11$$"/> + </actionGroup> + + <!-- Add Product11 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct11ToTheCart"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="11"/> + <argument name="productCountText" value="10 of 11 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertItemsCountInOrderSummary"> + <argument name="itemsText" value="10 of 11 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLinkInOrderSummary"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Click and open order summary tab--> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Shipping Page --> + <actionGroup ref="StorefrontAssertShippingAddressPageDisplayActionGroup" stepKey="assertShippingPageDisplay"/> + + <!-- Assert Product2 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct2InOrderSummary"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$20.00"/> + </actionGroup> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + + <!-- Assert Product5 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct5InOrderSummary"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$50.00"/> + </actionGroup> + + <!-- Assert Product6 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct6InOrderSummary"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$60.00"/> + </actionGroup> + + <!-- Assert Product7 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct7InOrderSummary"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$70.00"/> + </actionGroup> + + <!-- Assert Product8 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct8InOrderSummary"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$80.00"/> + </actionGroup> + + <!-- Assert Product9 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct9InOrderSummary"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$90.00"/> + </actionGroup> + + <!-- Assert Product10 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct10InOrderSummary"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$100.00"/> + </actionGroup> + + <!-- Assert Product11 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct11InOrderSummary"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$110.00"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml new file mode 100644 index 0000000000000..1f63565899f88 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Verify virtual products count in mini cart and summary block with custom display configuration"/> + <description value="Verify virtual products count in mini cart and summary block with custom display configuration"/> + <testCaseId value="MC-14724"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Set Mini Cart and Summary Block Display --> + <magentoCLI stepKey="setMaxDisplayCountForMiniCart" command="config:set checkout/options/max_items_display_count 2"/> + <magentoCLI stepKey="setMaxDisplayCountForOrderSummary" command="config:set checkout/sidebar/max_items_display_count 3"/> + <!--Create simple product--> + <createData entity="VirtualProduct" stepKey="virtualProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct4"> + <field key="price">40.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="virtualProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="virtualProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="virtualProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="virtualProduct4" stepKey="deleteProduct4"/> + <magentoCLI stepKey="setMaxDisplayCountForMiniCart" command="config:set checkout/options/max_items_display_count 10"/> + <magentoCLI stepKey="setMaxDisplayCountForOrderSummary" command="config:set checkout/sidebar/max_items_display_count 10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$virtualProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$virtualProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$virtualProduct4.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="4"/> + <argument name="productCountText" value="3 of 4 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$virtualProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$virtualProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertItemCountInOrderSummary"> + <argument name="itemsText" value="2 of 4 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLinkInOrderSummary"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Click and open order summary tab--> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$virtualProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml new file mode 100644 index 0000000000000..c5d1c34a93b32 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify checkout with different shipping and billing address and product with tier prices"/> + <description value="Checkout as a customer with different shipping and billing address and product with tier prices"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14713"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{EnablePaymentBankTransferConfigData.path}} {{EnablePaymentBankTransferConfigData.value}}" stepKey="enableBankTransferPayment"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">50.00</field> + </createData> + <actionGroup ref="filterAndSelectProduct" stepKey="filterAndSelectTheProduct"> + <argument name="productSku" value="$$simpleProduct.sku$$"/> + </actionGroup> + <actionGroup ref="ProductSetAdvancedTierFixedPricing" stepKey="setTierPrice"> + <argument name="website" value=""/> + <argument name="group" value=""/> + <argument name="quantity" value="3"/> + <argument name="price" value="Fixed"/> + <argument name="amount" value="24.00"/> + </actionGroup> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI command="config:set {{DisablePaymentBankTransferConfigData.path}} {{DisablePaymentBankTransferConfigData.value}}" stepKey="enableGuestCheckout"/> + + <!-- Sign out Customer from storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="UKCustomer.email"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="3"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill the guest form --> + <actionGroup ref="FillGuestCheckoutShippingAddressWithCountryActionGroup" stepKey="fillGuestForm"> + <argument name="customer" value="UKCustomer"/> + <argument name="customerAddress" value="updateCustomerUKAddress"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="waitForPlaceOrderButton"/> + <checkOption selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="selectBankTransfer"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.billingAddressNotSameBankTransferCheckbox}}" stepKey="waitForElementToBeVisible"/> + <uncheckOption selector="{{CheckoutPaymentSection.billingAddressNotSameBankTransferCheckbox}}" stepKey="uncheckSameBillingAndShippingAddress"/> + <conditionalClick selector="{{CheckoutShippingSection.editActiveAddressButton}}" dependentSelector="{{CheckoutShippingSection.editActiveAddressButton}}" visible="true" stepKey="clickEditButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + + <!-- Fill Billing Address --> + <actionGroup ref="StorefrontFillBillingAddressActionGroup" stepKey="fillBillingAddress"/> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="clickOnUpdateButton"/> + + <!--Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert Empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Register customer after checkout --> + <actionGroup ref="StorefrontRegisterCustomerAfterCheckoutActionGroup" stepKey="registerCustomer"/> + + <!-- Assert Billing Address in Storefront --> + <see selector="{{StorefrontCustomerAddressesSection.billingAddress}}" userInput="{{CustomerUKAddress.street[0]}}" stepKey="seeStreetNameInBillingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.billingAddress}}" userInput="{{CustomerUKAddress.city}}" stepKey="seeCityInBillingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.billingAddress}}" userInput="{{CustomerUKAddress.country}}" stepKey="seeCountryInBillingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.billingAddress}}" userInput="T: {{CustomerUKAddress.telephone}}" stepKey="seeTelephoneInBillingAddress"/> + + <!-- Assert Shipping Address in Storefront --> + <see selector="{{StorefrontCustomerAddressesSection.shippingAddress}}" userInput="{{updateCustomerUKAddress.street[0]}}" stepKey="seeStreetNameInShippingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.shippingAddress}}" userInput="{{updateCustomerUKAddress.city}}" stepKey="seeCityInShippingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.shippingAddress}}" userInput="{{updateCustomerUKAddress.country}}" stepKey="seeCountryInShippingAddress"/> + <see selector="{{StorefrontCustomerAddressesSection.shippingAddress}}" userInput="T: {{updateCustomerUKAddress.telephone}}" stepKey="seeTelephoneInShippingAddress"/> + + <!--Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForOrderIndexPageToLoad"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$87.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml new file mode 100644 index 0000000000000..34dc6617d25d5 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify UK customer checkout with different billing and shipping address and register customer after checkout"/> + <description value="Checkout as UK customer with different shipping/billing address and register checkout method"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14712"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">50.00</field> + </createData> + </before> + <after> + <!-- Sign out Customer from storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="UKCustomer.email"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill the guest form --> + <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> + <argument name="customer" value="UKCustomer"/> + <argument name="customerAddress" value="updateCustomerUKAddress"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="waitForElementToBeVisible"/> + <uncheckOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="uncheckSameBillingAndShippingAddress"/> + <conditionalClick selector="{{CheckoutShippingSection.editAddressButton}}" dependentSelector="{{CheckoutShippingSection.editAddressButton}}" visible="true" stepKey="clickEditButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + + <!-- Fill Billing Address --> + <actionGroup ref="StorefrontFillBillingAddressActionGroup" stepKey="fillBillingAddress"/> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="clickOnUpdateButton"/> + + <!--Place order --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Register customer after checkout --> + <actionGroup ref="StorefrontRegisterCustomerAfterCheckoutActionGroup" stepKey="registerCustomer"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$55.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml new file mode 100644 index 0000000000000..6ccb05bf4c4f7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutWithSpecialPriceProductsTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify customer checkout with special price simple and configurable products"/> + <description value="Create simple and configurable product with special prices and verify customer checkout"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14708"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + + <actionGroup ref="filterAndSelectProduct" stepKey="filterAndSelectTheProduct"> + <argument name="productSku" value="$$simpleProduct.sku$$"/> + </actionGroup> + + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPriceTopTheProduct"> + <argument name="price" value="9"/> + </actionGroup> + <click selector="{{AdminProductFormAdvancedPricingSection.save}}" stepKey="clickSaveProduct"/> + <waitForPageLoad time='60' stepKey="waitForProducrSaved"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSaveSuccessMessage"/> + + <!-- Create the configurable product with product Attribute options--> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="delete"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">20.00</field> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <field key="price">10.00</field> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <actionGroup ref="filterAndSelectProduct" stepKey="filterAndSelectTheProduct2"> + <argument name="productSku" value="$$createConfigChildProduct2.sku$$"/> + </actionGroup> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPriceTopTheProduct2"> + <argument name="price" value="9"/> + </actionGroup> + <click selector="{{AdminProductFormAdvancedPricingSection.save}}" stepKey="clickSaveProduct1"/> + <waitForPageLoad time='60' stepKey="waitForSpecialPriceProductSaved"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSaveSuccessMessage1"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigProduct2"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Add Configurable Product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption2.label$$"/> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Sign in using already existed customer details --> + <fillField selector="{{CheckoutShippingSection.emailAddress}}" userInput="$$createCustomer.email$$" stepKey="fillEmailAddress"/> + <waitForElementVisible selector="{{CheckoutShippingSection.password}}" stepKey="waitForPasswordFieldToBeVisible"/> + <fillField selector="{{CheckoutShippingSection.password}}" userInput="$$createCustomer.password$$" stepKey="fillPassword"/> + <click selector="{{CheckoutShippingSection.loginButton}}" stepKey="clickLoginButton"/> + <waitForPageLoad stepKey="waitForLoginPageToLoad"/> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$28.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml new file mode 100644 index 0000000000000..b0b72515611e8 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerCheckoutOnLoginWhenGuestCheckoutIsDisabledTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify customer is redirected to checkout on login when guest checkout is disabled"/> + <description value="Customer is redirected to checkout on login when guest checkout is disabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14703"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <magentoCLI command="config:set {{DisableGuestCheckoutConfigData.path}} {{DisableGuestCheckoutConfigData.value}}" stepKey="disableGuestCheckout"/> + <magentoCLI command="config:set {{DisableCustomerRedirectToDashboardConfigData.path}} {{DisableCustomerRedirectToDashboardConfigData.value}}" stepKey="disableCustomerRedirect"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">50.00</field> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <magentoCLI command="config:set {{EnableGuestCheckoutConfigData.path}} {{EnableGuestCheckoutConfigData.value}}" stepKey="enableGuestCheckout"/> + <magentoCLI command="config:set {{EnableCustomerRedirectToDashboardConfigData.path}} {{EnableCustomerRedirectToDashboardConfigData.value}}" stepKey="enableCustomerRedirect"/> + + <!-- Sign out Customer from storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + + <!--Fill the pop up sign form --> + <actionGroup ref="StorefrontCustomerSignInPopUpActionGroup" stepKey="customerSignIn"> + <argument name="customerEmail" value="$$createCustomer.email$$"/> + <argument name="customerPwd" value="$$createCustomer.password$$"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> + <waitForPageLoad stepKey="waitForShippingMethodSectionToLoad"/> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + + <!-- Verify order summary on payment page --> + <actionGroup ref="VerifyCheckoutPaymentOrderSummaryActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$50.00"/> + <argument name="orderSummaryShippingTotal" value="$5.00"/> + <argument name="orderSummaryTotal" value="$55.00"/> + </actionGroup> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Ship the order and assert the shipping status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index fadc9ec50ad8d..40b781df9b2ae 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -12,7 +12,7 @@ <annotations> <features value="Checkout"/> <stories value="Checkout via the Admin"/> - <title value="Customer Checkout"/> + <title value="Customer Checkout via the Admin"/> <description value="Should be able to place an order as a customer."/> <severity value="CRITICAL"/> <testCaseId value="MC-5922"/> @@ -72,6 +72,7 @@ <click stepKey="s81" selector="{{AdminOrdersGridSection.submitSearch22}}" /> <waitForPageLoad stepKey="s831"/> <click stepKey="s84" selector="{{AdminOrdersGridSection.firstRow}}" /> + <waitForPageLoad stepKey="waitForOrderToLoad"/> <see stepKey="s85" selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" /> <see stepKey="s87" selector="{{AdminOrderDetailsInformationSection.accountInformation}}" userInput="Customer" /> <see stepKey="s89" selector="{{AdminOrderDetailsInformationSection.accountInformation}}" userInput="$$simpleuscustomer.email$$" /> @@ -252,7 +253,7 @@ <click stepKey="clickNextButton" selector="{{CheckoutShippingMethodsSection.next}}" /> <waitForPageLoad stepKey="waitBillingForm"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> - <dontsee selector="{{CheckoutPaymentSection.paymentMethodByName('Check / Money order')}}" stepKey="paymentMethodDoesNotAvailable"/> + <dontSee selector="{{CheckoutPaymentSection.paymentMethodByName('Check / Money order')}}" stepKey="paymentMethodDoesNotAvailable"/> <!-- Fill UK Address and verify that payment available and checkout successful --> <uncheckOption selector="{{StorefrontCheckoutPaymentMethodSection.billingAddressSameAsShippingShared}}" stepKey="uncheckBillingAddressSameAsShippingCheckCheckBox"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml new file mode 100644 index 0000000000000..f7e54867b1ae4 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify customer checkout by following new customer registration when guest checkout is disabled"/> + <description value="Customer is redirected to checkout on login, follow the new Customer registration when guest checkout is disabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14704"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <magentoCLI command="config:set {{DisableGuestCheckoutConfigData.path}} {{DisableGuestCheckoutConfigData.value}}" stepKey="disableGuestCheckout"/> + <magentoCLI command="config:set {{DisableCustomerRedirectToDashboardConfigData.path}} {{DisableCustomerRedirectToDashboardConfigData.value}}" stepKey="disableCustomerRedirect"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">50.00</field> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <magentoCLI command="config:set {{EnableGuestCheckoutConfigData.path}} {{EnableGuestCheckoutConfigData.value}}" stepKey="enableGuestCheckout"/> + <magentoCLI command="config:set {{EnableCustomerRedirectToDashboardConfigData.path}} {{EnableCustomerRedirectToDashboardConfigData.value}}" stepKey="enableCustomerRedirect"/> + + <!-- Sign out Customer from storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="customerLogout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Create an account--> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.createAnAccount}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontCustomerSignInPopupFormSection.createAnAccount}}" stepKey="clickOnCreateAnAccountButton"/> + <waitForPageLoad stepKey="waitForCreateAccountPageToLoad"/> + + <!--Fill the registration form --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="createNewCustomerAccount"> + <argument name="customer" value="Simple_US_Customer" /> + </actionGroup> + <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}"/> + <waitForPageLoad stepKey="waitForCreateAccountButtonToLoad"/> + + <!--Assert customer information--> + <see stepKey="seeThankYouMessage" userInput="Thank you for registering with Main Website Store."/> + <see stepKey="seeFirstName" userInput="{{Simple_US_Customer.firstname}}" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" /> + <see stepKey="seeLastName" userInput="{{Simple_US_Customer.lastname}}" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" /> + <see stepKey="seeEmail" userInput="{{Simple_US_Customer.email}}" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" /> + + <!--Fill Address details --> + <click selector="{{CheckoutPaymentSection.addressBook}}" stepKey="goToAddressBook"/> + <fillField stepKey="fillPhoneNumber" userInput="{{US_Address_TX.telephone}}" selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{US_Address_TX.street[0]}}" selector="{{StorefrontCustomerAddressFormSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{US_Address_TX.city}}" selector="{{StorefrontCustomerAddressFormSection.city}}"/> + <selectOption stepKey="selectStateForAddress" userInput="{{CustomerAddressSimple.state}}" selector="{{StorefrontCustomerAddressFormSection.state}}"/> + <fillField stepKey="fillZip" userInput="{{US_Address_TX.postcode}}" selector="{{StorefrontCustomerAddressFormSection.zip}}"/> + <selectOption stepKey="selectCountryForAddress" userInput="{{US_Address_TX.country}}" selector="{{StorefrontCustomerAddressFormSection.country}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + + <!-- Open Edit and View from cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="openViewAndEditOption"/> + + <!-- Proceed to checkout --> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + + <!-- Verify order summary on payment page --> + <actionGroup ref="VerifyCheckoutPaymentOrderSummaryActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$50.00"/> + <argument name="orderSummaryShippingTotal" value="$5.00"/> + <argument name="orderSummaryTotal" value="$55.00"/> + </actionGroup> + + <!-- Assert Shipping Address --> + <actionGroup ref="CheckShipToInformationInCheckoutActionGroup" stepKey="assertShippingAddressDetails"> + <argument name="customerVar" value="Simple_US_Customer"/> + <argument name="customerAddressVar" value="US_Address_TX"/> + </actionGroup> + + <!-- Assert Billing Address --> + <actionGroup ref="CheckBillingAddressInCheckoutActionGroup" stepKey="assertBillingAddressDetails"> + <argument name="customerVar" value="Simple_US_Customer"/> + <argument name="customerAddressVar" value="US_Address_TX"/> + </actionGroup> + <see userInput="Flat Rate - Fixed" selector="{{CheckoutPaymentSection.shippingMethodInformation}}" stepKey="assertShippingMethodInformation"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Ship the order and assert the shipping status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml new file mode 100644 index 0000000000000..3a0ba2302a6dc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerLoginDuringCheckoutTest"> + <annotations> + <features value="OnePageCheckout"/> + <stories value="Customer Login during checkout"/> + <title value="Storefront customer login during checkout test"/> + <description value="Logging during checkout for customer without addresses in address book"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13097"/> + <group value="OnePageCheckout"/> + </annotations> + <before> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete simple product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Customer log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete customer --> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="CustomerEntityOne.email"/> + </actionGroup> + + <!-- Logout admin --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Go to Storefront as Guest and create new account --> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="createNewCustomerAccount"/> + + <!-- Sign Out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Add simple product to cart as Guest --> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Go to Checkout page --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + <!-- Input in field email and password for newly created customer; click Login button --> + <actionGroup ref="LoginAsCustomerOnCheckoutPageActionGroup" stepKey="customerLogin"> + <argument name="customer" value="CustomerEntityOne"/> + </actionGroup> + + <!-- Block with email is disappeared --> + <dontSeeElement selector="{{CheckoutShippingSection.email}}" stepKey="dontSeeEmailBlock"/> + + <!-- Shipping form is pre-filed with first name and last name --> + <seeInField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="seeCustomerFirstNameInField"/> + <seeInField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="seeCustomerLastNameInField"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteBundleProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteBundleProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..8f3ddbb27f62f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteBundleProductFromMiniShoppingCartTest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteBundleProductFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteBundleProduct"/> + <title value="Storefront Delete Bundle Product From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Bundle Product From Mini Shopping Cart Test"/> + <testCaseId value="MC-14682"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <!--Create Bundle product--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Product Quantity and add to the cart --> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTop"/> + <waitForPageLoad stepKey="waitForMiniCartPanelToAppear"/> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$10.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeProductInMiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + </actionGroup> + + <!--Remove an item from the cart using minicart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProductFromMiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$createBundleProduct.name$$)}}" stepKey="verifyAssertProductAbsentInMiniShoppingCart"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..f6357bcf4caa2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteConfigurableProductFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteConfigurableProduct"/> + <title value="Storefront Delete Configurable Product From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Configurable Product From Mini Shopping Cart Test"/> + <testCaseId value="MC-14681"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with three options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">10.00</field> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductThreeOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Add Configurable Product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption1.label$$"/> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeProductInMiniCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + + <!--Remove an item from the cart using minicart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProductFromMiniCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$createConfigProduct.name$$)}}" stepKey="verifyAssertProductAbsentInMiniShoppingCart"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteDownloadableProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteDownloadableProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..0fa503e1783b5 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteDownloadableProductFromMiniShoppingCartTest.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteDownloadableProductFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteConfigurableProduct"/> + <title value="Storefront Delete Downloadable Product From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Downloadable Product From Mini Shopping Cart Test"/> + <testCaseId value="MC-14683"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRateDefaultPrice"/> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Downloadable Product page --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Add Downloadable product to the cart --> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$" /> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!-- Assert product details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertMiniCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + <argument name="productPrice" value="$123.00"/> + <argument name="cartSubtotal" value="123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeProductInMiniCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + + <!--Remove an item from the cart using minicart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProductFromMiniCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$createDownloadableProduct.name$$)}}" stepKey="verifyAssertProductAbsentInMiniShoppingCart"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteProductsWithCartItemsDisplayDefaultLimitationFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteProductsWithCartItemsDisplayDefaultLimitationFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..cca5268564b12 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteProductsWithCartItemsDisplayDefaultLimitationFromMiniShoppingCartTest.xml @@ -0,0 +1,285 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteProductsWithCartItemsDisplayDefaultLimitationFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteProductsWithCartItemsDisplayDefaultLimitation"/> + <title value="Storefront Delete Products With Cart Items Display Default Limitation From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Products With Cart Items Display Default Limitation From Mini Shopping Cart Test"/> + <testCaseId value="MC-14687"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Create 10 simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product1 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <!--Add Product1 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!--Open Product2 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <!--Add Product2 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!--Open Product3 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + <!--Add Product3 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!--Open Product4 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + <!--Add Product4 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!--Open Product5 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + <!--Add Product5 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!--Open Product6 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + <!--Add Product6 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!--Open Product7 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + <!--Add Product7 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!--Open Product8 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + <!--Add Product8 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!--Open Product9 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + <!--Add Product9 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!--Open Product10 page in StoreFront--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + <!--Add Product10 to the cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!--Open Mini Cart--> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!--Assert Product Count in Mini Cart and verify AssertVisibleItemsQtyMessageInMiniShoppingCart--> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="10"/> + <argument name="productCountText" value="10 Items in Cart"/> + </actionGroup> + + <!--Assert Product1 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product2 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product3 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product4 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product5 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product6 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product7 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product8 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product9 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <!--Assert Product10 in Mini Cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Remove products from minicart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct1FromMiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct2FromMiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct3FromMiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct4FromMiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct5FromMiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct6FromMiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct7FromMiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct8FromMiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct9FromMiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProduct10FromMiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify EmptyCartMessage and AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct1.name$$)}}" stepKey="verifyAssertProduct1AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct2.name$$)}}" stepKey="verifyAssertProduct2AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct3.name$$)}}" stepKey="verifyAssertProduct3AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct4.name$$)}}" stepKey="verifyAssertProduct4AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct5.name$$)}}" stepKey="verifyAssertProduct5AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct6.name$$)}}" stepKey="verifyAssertProduct6AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct7.name$$)}}" stepKey="verifyAssertProduct7AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct8.name$$)}}" stepKey="verifyAssertProduct8AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct9.name$$)}}" stepKey="verifyAssertProduct9AbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct10.name$$)}}" stepKey="verifyAssertProduct10AbsentInMiniShoppingCart"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleAndVirtualProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleAndVirtualProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..b8092ccdcdce7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleAndVirtualProductFromMiniShoppingCartTest.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteSimpleAndVirtualProductFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteSimpleAndVirtualProduct"/> + <title value="Storefront Delete Simple And Virtual Product From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Simple And Virtual Product From Mini Shopping Cart Test"/> + <testCaseId value="MC-14685"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + <!--Create virtual product--> + <createData entity="VirtualProduct" stepKey="virtualProduct"> + <field key="price">20.00</field> + </createData> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="virtualProduct" stepKey="deleteVirtualproduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Add Simple Product to the cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addSimpleProductToCart"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + <!-- Add virtual Product to the cart --> + <amOnPage url="{{StorefrontProductPage.url($$virtualProduct.name$$)}}" stepKey="amOnStorefrontVirtualProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + </actionGroup> + + <!-- Assert Simple and Virtual products in mini cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProductInMiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$30.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertVirtualProductInMiniCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$30.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Select mini Cart and verify Simple and Virtual products names in cart--> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeSimpleProductInMiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeVirtualProductInMiniCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + </actionGroup> + + <!--Remove Simple and Virtual products from mini cart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProductFromMiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeVirtualProductFromMiniCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify EmptyCartMessage and AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct.name$$)}}" stepKey="verifyAssertSimpleProductAbsentInMiniShoppingCart"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$virtualProduct.name$$)}}" stepKey="verifyAssertVirtualProductAbsentInMiniShoppingCart"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000..05198060f5de4 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteSimpleProductFromMiniShoppingCartTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontDeleteSimpleProductFromMiniShoppingCartTest"> + <annotations> + <stories value="DeleteSimpleProduct"/> + <title value="Storefront Delete Simple Product From Mini Shopping Cart Test"/> + <description value="Test log in to Shopping Cart and Delete Simple Product From Mini Shopping Cart Test"/> + <testCaseId value="MC-14686"/> + <severity value="CRITICAL"/> + <group value="Shopping Cart"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Add Simple Product to the cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$10.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="assertOneProductNameInMiniCart" stepKey="seeProductInMiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + + <!--Remove an item from the cart using minicart--> + <actionGroup ref="removeProductFromMiniCart" stepKey="removeProductFromMiniCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + + <!--Check the minicart is empty and verify AssertProductAbsentInMiniShoppingCart--> + <actionGroup ref="assertMiniCartEmpty" stepKey="miniCartEnpty"/> + <dontSee selector="{{StorefrontMinicartSection.productLinkByName($$simpleProduct.name$$)}}" stepKey="verifyAssertProductAbsentInMiniShoppingCart"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutForSpecificCountriesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutForSpecificCountriesTest.xml new file mode 100644 index 0000000000000..f6a332a529dc9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutForSpecificCountriesTest.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutForSpecificCountriesTest"> + <annotations> + <features value="One Page Checkout"/> + <stories value="Checkout for Specific Countries"/> + <title value="Storefront guest checkout for specific countries test"/> + <description value="Checkout flow if shipping rates are not available"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13414"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + + <!-- Enable free shipping to specific country - Afghanistan --> + <magentoCLI command="config:set {{EnableFreeShippingConfigData.path}} {{EnableFreeShippingConfigData.value}}" stepKey="enableFreeShipping"/> + <magentoCLI command="config:set {{EnableFreeShippingToSpecificCountriesConfigData.path}} {{EnableFreeShippingToSpecificCountriesConfigData.value}}" stepKey="allowFreeShippingSpecificCountries"/> + <magentoCLI command="config:set {{EnableFreeShippingToAfghanistanConfigData.path}} {{EnableFreeShippingToAfghanistanConfigData.value}}" stepKey="enableFreeShippingToAfghanistan"/> + + <!-- Enable flat rate shipping to specific country - Afghanistan --> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="config:set {{EnableFlatRateToSpecificCountriesConfigData.path}} {{EnableFlatRateToSpecificCountriesConfigData.value}}" stepKey="allowFlatRateSpecificCountries"/> + <magentoCLI command="config:set {{EnableFlatRateToAfghanistanConfigData.path}} {{EnableFlatRateToAfghanistanConfigData.value}}" stepKey="enableFlatRateToAfghanistan"/> + </before> + <after> + <!-- Rollback all configurations --> + <magentoCLI command="config:set {{DisableFreeShippingConfigData.path}} {{DisableFreeShippingConfigData.value}}" stepKey="disableFreeShipping"/> + <magentoCLI command="config:set {{EnableFreeShippingToAllAllowedCountriesConfigData.path}} {{EnableFreeShippingToAllAllowedCountriesConfigData.value}}" stepKey="allowFreeShippingToAllCountries"/> + <magentoCLI command="config:set {{EnableFlatRateToAllAllowedCountriesConfigData.path}} {{EnableFlatRateToAllAllowedCountriesConfigData.value}}" stepKey="allowFlatRateToAllCountries"/> + + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Add product to cart --> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Go to checkout page --> + <actionGroup ref="OpenStoreFrontCheckoutShippingPageActionGroup" stepKey="openCheckoutShippingPage"/> + + <!-- Assert shipping methods are unavailable --> + <actionGroup ref="AssertStoreFrontShippingMethodUnavailableActionGroup" stepKey="dontSeeFlatRateShippingMethod"> + <argument name="shippingMethodName" value="Flat Rate"/> + </actionGroup> + <actionGroup ref="AssertStoreFrontShippingMethodUnavailableActionGroup" stepKey="dontFreeShippingMethod"/> + + <!-- Assert no quotes message --> + <actionGroup ref="AssertStoreFrontNoQuotesMessageActionGroup" stepKey="assertNoQuotesMessage"/> + + <!-- Assert Next button --> + <dontSeeElement selector="{{CheckoutShippingMethodsSection.next}}" stepKey="dontSeeNextButton"/> + + <!-- Fill form with valid data for US > California --> + <selectOption selector="{{CheckoutShippingSection.country}}" userInput="{{US_Address_CA.country}}" stepKey="selectCountry"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{US_Address_CA.state}}" stepKey="selectState"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{US_Address_CA.postcode}}" stepKey="fillPostcode"/> + + <!-- Assert shipping methods are unavailable for US > California --> + <actionGroup ref="AssertStoreFrontShippingMethodUnavailableActionGroup" stepKey="dontSeeFlatRateShippingMtd"> + <argument name="shippingMethodName" value="Flat Rate"/> + </actionGroup> + <actionGroup ref="AssertStoreFrontShippingMethodUnavailableActionGroup" stepKey="dontFreeShippingMtd"/> + + <!-- Assert no quotes message for US > California --> + <actionGroup ref="AssertStoreFrontNoQuotesMessageActionGroup" stepKey="assertNoQuotesMsg"/> + + <!-- Assert Next button for US > California --> + <dontSeeElement selector="{{CheckoutShippingMethodsSection.next}}" stepKey="dontSeeNextBtn"/> + + <!-- Fill form for specific country - Afghanistan --> + <selectOption selector="{{CheckoutShippingSection.country}}" userInput="Afghanistan" stepKey="selectSpecificCountry"/> + + <!-- Assert shipping methods are available --> + <actionGroup ref="AssertStoreFrontShippingMethodAvailableActionGroup" stepKey="seeFlatRateShippingMethod"/> + <actionGroup ref="AssertStoreFrontShippingMethodAvailableActionGroup" stepKey="seeFreeShippingMethod"> + <argument name="shippingMethodName" value="Free Shipping"/> + </actionGroup> + + <!-- Assert Next button is available --> + <seeElement selector="{{CheckoutShippingMethodsSection.next}}" stepKey="seeNextButton"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index ff61b3be08af1..a77341b8697b5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -12,10 +12,10 @@ <annotations> <features value="Checkout"/> <stories value="Checkout via Guest Checkout"/> - <title value="Guest Checkout"/> - <description value="Should be able to place an order as a Guest."/> + <title value="Guest Checkout - guest should be able to place an order"/> + <description value="Should be able to place an order as a Guest"/> <severity value="CRITICAL"/> - <testCaseId value="MAGETWO-72094"/> + <testCaseId value="MC-12825"/> <group value="checkout"/> </annotations> <before> @@ -62,6 +62,7 @@ <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeAdminOrderStatus"/> <see selector="{{AdminOrderDetailsInformationSection.accountInformation}}" userInput="{{CustomerEntityOne.fullname}}" stepKey="seeAdminOrderGuest"/> <see selector="{{AdminOrderDetailsInformationSection.accountInformation}}" userInput="{{CustomerEntityOne.email}}" stepKey="seeAdminOrderEmail"/> @@ -78,6 +79,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-97001"/> <group value="checkout"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> <magentoCLI stepKey="disableSidebar" command="config:set checkout/sidebar/display 0" /> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml new file mode 100644 index 0000000000000..c8c6407363b4c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify guest checkout using free shipping and tax variations"/> + <description value="Verify guest checkout using free shipping and tax variations"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14709"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-16684"/> + </skip> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRate"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <createData entity="MinimumOrderAmount100" stepKey="minimumOrderAmount100"/> + <createData entity="taxRate_US_NY_8_1" stepKey="createTaxRule"/> + + <!--Create Simple Product --> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + + <!-- Create the configurable product with product Attribute options--> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addToDefaultSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">10.00</field> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Create Bundle Product --> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">200.00</field> + </createData> + <!--Create Bundle product with multi select option--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigProduct1"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule1"/> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DefaultMinimumOrderAmount" stepKey="defaultMinimumOrderAmount"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create a Tax Rule --> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRuleIndex1"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + + <!-- Create a tax rule with defaults --> + <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$createTaxRule.code$$" stepKey="fillTaxRateSearch"/> + <wait stepKey="waitForSearch" time="5" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption($$createTaxRule.code$$)}}" stepKey="selectNeededItem" /> + <click selector="{{AdminTaxRuleFormSection.save}}" stepKey="saveTaxRule" /> + <waitForPageLoad stepKey="waitForTaxRuleSaved" /> + + <!-- Verify we see success message --> + <see selector="{{AdminTaxRuleGridSection.successMessage}}" userInput="You saved the tax rule." stepKey="assertTaxRuleSuccessMessage" /> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Add Configurable Product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption1.label$$"/> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openBundleProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Two Products, enter the quantity and add product to the cart --> + <selectOption selector="{{StorefrontBundledSection.multiSelectOption}}" userInput="$$simpleProduct1.name$$ +$100.00" stepKey="selectOption"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"> + <argument name="address" value="US_Address_NY_Default_Shipping"/> + </actionGroup> + <reloadPage stepKey="reloadThePage"/> + <waitForPageLoad stepKey="waitForPageToReload"/> + <waitForText selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$9.60" time="90" stepKey="waitForTaxAmount"/> + + <!--Select Free Shipping and proceed to checkout --> + <click selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="selectFlatRateShippingMethod"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill Guest form --> + <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillTheSignInForm"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="customerAddress" value="US_Address_NY_Default_Shipping"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$129.60" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert customer order address --> + <actionGroup ref="AssertOrderAddressInformationActionGroup" stepKey="assertCustomerInformation"> + <argument name="customer" value=""/> + <argument name="shippingAddress" value="US_Address_NY_Default_Shipping"/> + <argument name="billingAddress" value="US_Address_NY_Default_Shipping"/> + <argument name="customerGroup" value=""/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml new file mode 100644 index 0000000000000..33ec099aa2ace --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify guest checkout with virtual product using coupon for not logged in customers with Zero Subtotal"/> + <description value="Checkout with virtual product using coupon for not logged in customers with Zero Subtotal"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14710"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="VirtualProduct" stepKey="virtualProduct"> + <field key="price">50.00</field> + </createData> + <createData entity="SalesRuleNoCouponWithFixedDiscount" stepKey="createSalesRule"/> + </before> + <after> + <deleteData createDataKey="virtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openVirtualProductPageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct$$"/> + </actionGroup> + + <!-- Add Product to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + + <!-- Assert Discount and proceed to checkout --> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$50.00" stepKey="seeDiscountTotal"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Fill Customer Information --> + <fillField selector="{{CheckoutShippingSection.emailAddress}}" userInput="{{Simple_US_Customer.email}}" stepKey="enterEmail"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoading" /> + <fillField selector="{{CheckoutPaymentSection.guestFirstName}}" userInput="{{Simple_US_Customer.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutPaymentSection.guestLastName}}" userInput="{{Simple_US_Customer.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutPaymentSection.guestStreet}}" userInput="{{US_Address_TX.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutPaymentSection.guestCity}}" userInput="{{US_Address_TX.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutPaymentSection.guestRegion}}" userInput="{{US_Address_TX.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutPaymentSection.guestPostcode}}" userInput="{{US_Address_TX.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutPaymentSection.guestTelephone}}" userInput="{{US_Address_TX.telephone}}" stepKey="enterTelephone"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.update}}" stepKey="waitForUpdateButton"/> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="clickOnUpdateButton"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$0.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutJsValidationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutJsValidationTest.xml new file mode 100644 index 0000000000000..8ed8e590eb229 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutJsValidationTest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontOnePageCheckoutJsValidationTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout"/> + <title value="Js validation error messages must be absent for required fields after checkout start."/> + <description value="Js validation error messages must be absent for required fields after checkout start."/> + <severity value="MAJOR" /> + <testCaseId value="MC-18312" /> + <group value="shoppingCart" /> + <group value="mtf_migrated" /> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <actionGroup ref="AddSimpleProductToCart" stepKey="addToCartFromStorefrontProductPage"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="guestGoToCheckout"/> + <actionGroup ref="StorefrontAssertNoValidationErrorForCheckoutAddressFieldsActionGroup" stepKey="seeNoValidationErrors"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml index 3401369a8c749..5e7e76ae4f02a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest"> <annotations> <features value="Checkout"/> + <stories value="Minicart"/> <title value="Checking Product name in Minicart and on Checkout page with different store views"/> <description value="Checking Product name in Minicart and on Checkout page with different store views"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml new file mode 100644 index 0000000000000..285dc28cb520e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify product quantity changes in backend after customer checkout"/> + <description value="Checkout as UK customer with bank transfer payment method and verify product quantity reduced after order processed "/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14707"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <magentoCLI command="config:set {{EnablePaymentBankTransferConfigData.path}} {{EnablePaymentBankTransferConfigData.value}}" stepKey="enableBankTransferPayment"/> + <magentoCLI command="config:set {{EnableCatalogInventoryConfigData.path}} {{EnableCatalogInventoryConfigData.value}}" stepKey="enableCatalogInventoryStock"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + </before> + <after> + <magentoCLI command="config:set {{DisablePaymentBankTransferConfigData.path}} {{DisablePaymentBankTransferConfigData.value}}" stepKey="enableGuestCheckout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="100"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill Customer Sign In Information --> + <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> + <argument name="customer" value="UKCustomer"/> + <argument name="customerAddress" value="updateCustomerUKAddress"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="selectBankTransfer"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$1,500.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + + <!-- Assert Product Quantity in backend reduced after order processed --> + <actionGroup ref="filterAndSelectProduct" stepKey="filterAndSelectTheProduct"> + <argument name="productSku" value="$$simpleProduct.sku$$"/> + </actionGroup> + <waitForElementVisible selector="{{AdminProductFormSection.productName}}" stepKey="waitForProductDetailsToLoad"/> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="$$simpleProduct.name$$" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="0" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="Out of Stock" stepKey="seeProductStockStatus"/> + + <!-- Assert Product is Out of Stock in frontend --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="assertProductInStorefront"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="Out Of Stock" stepKey="seeProductDisplayedAsOutOfStock"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml new file mode 100644 index 0000000000000..1db460de44996 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontRefreshPageDuringGuestCheckoutTest"> + <annotations> + <features value="Checkout"/> + <stories value="Guest checkout"/> + <title value="Storefront refresh page during guest checkout test"/> + <description value="Address is not lost for guest checkout if guest refresh page during checkout"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12084"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create simple product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!-- Delete simple product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Logout admin --> + <actionGroup ref="logout" stepKey="logoutAsAdmin"/> + </after> + <!-- Add simple product to cart as Guest --> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Go to Checkout page --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + + <!-- Fill email field and addresses form and go next --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + + <!-- Refresh page --> + <reloadPage stepKey="refreshPage"/> + + <!-- Click Place Order and assert order is placed --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!-- Order review page has address that was created during checkout --> + <amOnPage url="{{AdminOrderPage.url({$grabOrderNumber})}}" stepKey="navigateToOrderPage"/> + <waitForPageLoad stepKey="waitForCreatedOrderPage"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{CustomerAddressSimple.street[0]}} {{CustomerAddressSimple.city}}, {{CustomerAddressSimple.state}}, {{CustomerAddressSimple.postcode}}" stepKey="checkShippingAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml new file mode 100644 index 0000000000000..482e2fb6233a6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUKCustomerCheckoutWithCouponTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify UK customer checkout with Virtual and Downloadable products using coupon"/> + <description value="Checkout as UK Customer with virtual product and downloadable product using coupon"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14705"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="downloadable:domains:add" arguments="example.com static.magento.com" stepKey="addDownloadableDomain"/> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"> + <field key="price">20.00</field> + </createData> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="downloadableLink2" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct"> + <field key="price">10.00</field> + </createData> + <createData entity="UKCustomer" stepKey="createCustomer"/> + <createData entity="SalesRuleSpecificCouponAndByPercent" stepKey="createSalesRule"/> + <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> + <requiredEntity createDataKey="createSalesRule"/> + </createData> + </before> + <after> + <magentoCLI command="downloadable:domains:remove" arguments="example.com static.magento.com" stepKey="removeDownloadableDomain"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="virtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Downloadable Product page --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Add Downloadable product to the cart --> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$" /> + </actionGroup> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openVirtualProductPageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct$$"/> + </actionGroup> + + <!-- Add Product to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Apply Coupon --> + <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyDiscount"> + <argument name="coupon" value="$$createCouponForCartPriceRule$$"/> + </actionGroup> + + <!-- Assert Discount and proceed to checkout --> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$15.00" stepKey="seeDiscountTotal"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + + <!--Fill the pop up sign form --> + <actionGroup ref="StorefrontCustomerSignInPopUpActionGroup" stepKey="customerSignIn"> + <argument name="customerEmail" value="$$createCustomer.email$$"/> + <argument name="customerPwd" value="$$createCustomer.password$$"/> + </actionGroup> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout1"/> + <waitForPageLoad stepKey="waitForShippingMethodSectionToLoad"/> + + <!-- Click and open order summary tab--> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad5"/> + + <!-- Assert Product displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$virtualProduct.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$10.00"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutPaymentSection.update}}" stepKey="waitForUpdateButton"/> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="clickOnUpdateButton"/> + + <!-- Place the order and Verify Success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + + <!-- Login to Admin Page --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="$$createCustomer.fullname$$" stepKey="seeCustomerNameInGrid"/> + <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="$15.00" stepKey="seeGrandTotalInGrid"/> + <see selector="{{AdminOrdersGridSection.firstRow}}" userInput="Pending" stepKey="seeStatusIdInGrid"/> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnOrderViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$15.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml new file mode 100644 index 0000000000000..deab32aede324 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest"> + <annotations> + <stories value="Checkout"/> + <title value="Checkout as UK guest with condition available product quantity equals to ordered product quantity"/> + <description value="Checkout as UK guest with condition available product quantity equals to ordered product quantity"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14711"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"> + <field key="price">10.00</field> + </createData> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!--Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="100"/> + </actionGroup> + + <!--Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill Customer Sign In Information --> + <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"> + <argument name="customer" value="UKCustomer"/> + <argument name="customerAddress" value="UK_Not_Default_Address"/> + </actionGroup> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$1,500.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + + <!-- Assert Product Quantity in backend reduced after order processed --> + <actionGroup ref="filterAndSelectProduct" stepKey="filterAndSelectTheProduct"> + <argument name="productSku" value="$$simpleProduct.sku$$"/> + </actionGroup> + <waitForElementVisible selector="{{AdminProductFormSection.productName}}" stepKey="waitForProductDetailsToLoad"/> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="$$simpleProduct.name$$" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="0" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="Out of Stock" stepKey="seeProductStockStatus"/> + + <!-- Assert Product is Out of Stock in frontend --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="assertProductInStorefront"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="Out Of Stock" stepKey="seeProductDisplayedAsOutOfStock"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml new file mode 100644 index 0000000000000..6d5f79f3aadf4 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest"> + <annotations> + <stories value="Checkout"/> + <title value="Verify US customer checkout using coupon and bank transfer payment method"/> + <description value="Checkout as US customer using coupon and bank transfer payment method"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14706"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <magentoCLI command="config:set {{EnablePaymentBankTransferConfigData.path}} {{EnablePaymentBankTransferConfigData.value}}" stepKey="enableBankTransferPayment"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">50.00</field> + </createData> + <createData entity="SalesRuleSpecificCouponAndByPercent" stepKey="createSalesRule"/> + <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> + <requiredEntity createDataKey="createSalesRule"/> + </createData> + </before> + <after> + <magentoCLI command="config:set {{DisablePaymentBankTransferConfigData.path}} {{DisablePaymentBankTransferConfigData.value}}" stepKey="disableBankTransferPayment"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + + <!-- Add product to the cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Open View and edit --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="clickMiniCart"/> + + <!-- Fill the Estimate Shipping and Tax section --> + <actionGroup ref="CheckoutFillEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxFields"/> + + <!-- Apply Coupon --> + <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyDiscount"> + <argument name="coupon" value="$$createCouponForCartPriceRule$$"/> + </actionGroup> + + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Fill the guest form --> + <actionGroup ref="FillGuestCheckoutShippingAddressFormActionGroup" stepKey="fillGuestForm"/> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.next}}" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="waitForPlaceOrderButton"/> + <checkOption selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="selectBankTransfer"/> + + <!-- Place order and Assert success message --> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> + + <!-- Assert empty Mini Cart --> + <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> + + <!-- Open Order Index Page --> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrders"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Filter Order using orderId and assert order--> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.viewLink('$orderId')}}" stepKey="clickOnViewLink"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + + <!-- Assert Grand Total --> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$25.00" stepKey="seeGrandTotal"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderStatus"/> + + <!-- Ship the order and assert the status --> + <actionGroup ref="AdminShipThePendingOrderActionGroup" stepKey="shipTheOrder"/> + + <!-- Assert order buttons --> + <actionGroup ref="AdminAssertOrderAvailableButtonsActionGroup" stepKey="assertOrderButtons"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml index b4747a6bf7273..d27cb83a8ca5b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-58179"/> <group value="checkout"/> + <skip> + <issueId value="MC-16684"/> + </skip> </annotations> <before> <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml index 423f4049f6722..72f6cf95a6fbe 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml @@ -11,9 +11,11 @@ <test name="StorefrontUpdateShoppingCartSimpleProductQtyTest"> <annotations> <features value="Checkout"/> + <stories value="Shopping cart"/> <title value="Check updating shopping cart while updating items qty"/> <description value="Check updating shopping cart while updating items qty"/> <testCaseId value="MC-14731" /> + <severity value="AVERAGE"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml index 84080b04c80ee..7a653f13c4ee5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml @@ -11,9 +11,11 @@ <test name="StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest"> <annotations> <features value="Checkout"/> + <stories value="Shopping cart"/> <title value="Check updating shopping cart while updating qty of items with custom options"/> <description value="Check updating shopping cart while updating qty of items with custom options"/> <testCaseId value="MC-14732" /> + <severity value="AVERAGE"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml index 1b27e1d53adad..65f5dd365b215 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml @@ -11,10 +11,12 @@ <test name="StorefrontValidateEmailOnCheckoutTest"> <annotations> <features value="Checkout"/> - <title value="Email validation for Guest on checkout flow"/> - <description value="Email validation for Guest on checkout flow"/> + <stories value="Guest Checkout e-mail validation"/> + <title value="Guest e-mail address validation on Checkout process"/> + <description value="Guest should not be able to place an order when invalid e-mail address provided"/> <stories value="Guest Checkout"/> - <testCaseId value="MC-14695" /> + <testCaseId value="MC-14695"/> + <severity value="CRITICAL"/> <group value="checkout"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> @@ -28,27 +30,27 @@ </after> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductStorefront"> - <argument name="productUrl" value="$$simpleProduct.custom_attributes[url_key]$$" /> + <argument name="productUrl" value="$$simpleProduct.custom_attributes[url_key]$$"/> </actionGroup> - <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage" /> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> - <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage" /> - <actionGroup ref="AssertStorefrontEmailTooltipContentOnCheckoutActionGroup" stepKey="assertEmailTooltipContent" /> - <actionGroup ref="AssertStorefrontEmailNoteMessageOnCheckoutActionGroup" stepKey="assertEmailNoteMessage" /> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/> + <actionGroup ref="AssertStorefrontEmailTooltipContentOnCheckoutActionGroup" stepKey="assertEmailTooltipContent"/> + <actionGroup ref="AssertStorefrontEmailNoteMessageOnCheckoutActionGroup" stepKey="assertEmailNoteMessage"/> <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailFirstAttempt"> - <argument name="email" value="John" /> + <argument name="email" value="John"/> </actionGroup> - <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageFirstAttempt" /> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageFirstAttempt"/> <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailSecondAttempt"> - <argument name="email" value="johndoe#example.com" /> + <argument name="email" value="johndoe#example.com"/> </actionGroup> - <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageSecondAttempt" /> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageSecondAttempt"/> <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailThirdAttempt"> - <argument name="email" value="johndoe@example.c" /> + <argument name="email" value="johndoe@example.c"/> </actionGroup> - <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageThirdAttempt" /> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageThirdAttempt"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php index ea841e86586ba..df5c255398ebd 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php @@ -163,6 +163,31 @@ public function testSavePaymentInformationAndPlaceOrderWithLocolizedException() $this->model->savePaymentInformationAndPlaceOrder($cartId, $paymentMock, $billingAddressMock); } + /** + * Test for save payment and place order with new billing address + * + * @return void + */ + public function testSavePaymentInformationAndPlaceOrderWithNewBillingAddress(): void + { + $cartId = 100; + $quoteBillingAddressId = 1; + $customerId = 1; + $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); + $quoteBillingAddress = $this->createMock(\Magento\Quote\Model\Quote\Address::class); + $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); + + $quoteBillingAddress->method('getCustomerId')->willReturn($customerId); + $quoteMock->method('getBillingAddress')->willReturn($quoteBillingAddress); + $quoteBillingAddress->method('getId')->willReturn($quoteBillingAddressId); + $this->cartRepositoryMock->method('getActive')->with($cartId)->willReturn($quoteMock); + + $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); + $billingAddressMock->expects($this->once())->method('setCustomerId')->with($customerId); + $this->assertTrue($this->model->savePaymentInformation($cartId, $paymentMock, $billingAddressMock)); + } + /** * @param int $cartId * @param \PHPUnit_Framework_MockObject_MockObject $billingAddressMock @@ -179,9 +204,10 @@ private function getMockForAssignBillingAddress($cartId, $billingAddressMock) ['setLimitCarrier', 'getShippingMethod', 'getShippingRateByCode'] ); $this->cartRepositoryMock->expects($this->any())->method('getActive')->with($cartId)->willReturn($quoteMock); - $quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($quoteBillingAddress); + $quoteMock->method('getBillingAddress')->willReturn($quoteBillingAddress); $quoteMock->expects($this->once())->method('getShippingAddress')->willReturn($quoteShippingAddress); $quoteBillingAddress->expects($this->once())->method('getId')->willReturn($billingAddressId); + $quoteBillingAddress->expects($this->once())->method('getId')->willReturn($billingAddressId); $quoteMock->expects($this->once())->method('removeAddress')->with($billingAddressId); $quoteMock->expects($this->once())->method('setBillingAddress')->with($billingAddressMock); $quoteMock->expects($this->once())->method('setDataChanges')->willReturnSelf(); diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index 8117def0377a4..9e538292ab4a8 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-catalog-inventory": "100.3.*", @@ -42,5 +42,5 @@ "Magento\\Checkout\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Checkout/view/frontend/templates/button.phtml b/app/code/Magento/Checkout/view/frontend/templates/button.phtml index c3edfe30f8bdd..b0087794ea850 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/button.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/button.phtml @@ -3,15 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Checkout\Block\Onepage\Success */ ?> <?php if ($block->getCanViewOrder() && $block->getCanPrintOrder()) :?> - <a href="<?= /* @escapeNotVerified */ $block->getPrintUrl() ?>" target="_blank" class="print"> - <?= /* @escapeNotVerified */ __('Print receipt') ?> + <a href="<?= $block->escapeUrl($block->getPrintUrl()) ?>" target="_blank" class="print"> + <?= $block->escapeHtml(__('Print receipt')) ?> </a> <?= $block->getChildHtml() ?> <?php endif;?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart.phtml index 929f053febfc7..e71ea8c66288c 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart.phtml @@ -12,7 +12,9 @@ */ if ($block->getItemsCount()) { + // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $block->getChildHtml('with-items'); } else { + // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $block->getChildHtml('no-items'); } diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/additional/info.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/additional/info.phtml index cb92e62f1f0c8..b807a6db019b5 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/additional/info.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/additional/info.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @@ -16,6 +14,7 @@ <?php $name = $block->getNameInLayout(); foreach ($block->getChildNames($name) as $childName) { + // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $block->getChildBlock($childName)->setItem($block->getItem())->toHtml(); } ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index 7c6a56ec8c699..bf8490affea0c 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -4,19 +4,20 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var \Magento\Framework\View\Element\AbstractBlock $block */ ?> -<div class="block discount" id="block-discount" data-mage-init='{"collapsible":{"openedState": "active", "saveState": false}}'> +<div class="block discount" + id="block-discount" + data-mage-init='{"collapsible":{"openedState": "active", "saveState": false}}' +> <div class="title" data-role="title"> - <strong id="block-discount-heading" role="heading" aria-level="2"><?= /* @escapeNotVerified */ __('Apply Discount Code') ?></strong> + <strong id="block-discount-heading" role="heading" aria-level="2"><?= $block->escapeHtml(__('Apply Discount Code')) ?></strong> </div> <div class="content" data-role="content" aria-labelledby="block-discount-heading"> <form id="discount-coupon-form" - action="<?= /* @escapeNotVerified */ $block->getUrl('checkout/cart/couponPost') ?>" + action="<?= $block->escapeUrl($block->getUrl('checkout/cart/couponPost')) ?>" method="post" data-mage-init='{"discountCode":{"couponCodeSelector": "#coupon_code", "removeCouponSelector": "#remove-coupon", @@ -25,27 +26,36 @@ <div class="fieldset coupon<?= strlen($block->getCouponCode()) ? ' applied' : '' ?>"> <input type="hidden" name="remove" id="remove-coupon" value="0" /> <div class="field"> - <label for="coupon_code" class="label"><span><?= /* @escapeNotVerified */ __('Enter discount code') ?></span></label> + <label for="coupon_code" class="label"><span><?= $block->escapeHtml(__('Enter discount code')) ?></span></label> <div class="control"> - <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?= $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtml(__('Enter discount code')) ?>" <?php if (strlen($block->getCouponCode())): ?> disabled="disabled" <?php endif; ?> /> + <input type="text" + class="input-text" + id="coupon_code" + name="coupon_code" + value="<?= $block->escapeHtmlAttr($block->getCouponCode()) ?>" + placeholder="<?= $block->escapeHtmlAttr(__('Enter discount code')) ?>" + <?php if (strlen($block->getCouponCode())) :?> + disabled="disabled" + <?php endif; ?> + /> </div> </div> <div class="actions-toolbar"> - <?php if (!strlen($block->getCouponCode())): ?> + <?php if (!strlen($block->getCouponCode())) :?> <div class="primary"> - <button class="action apply primary" type="button" value="<?= /* @escapeNotVerified */ __('Apply Discount') ?>"> - <span><?= /* @escapeNotVerified */ __('Apply Discount') ?></span> + <button class="action apply primary" type="button" value="<?= $block->escapeHtmlAttr(__('Apply Discount')) ?>"> + <span><?= $block->escapeHtml(__('Apply Discount')) ?></span> </button> </div> - <?php else: ?> + <?php else :?> <div class="primary"> - <button type="button" class="action cancel primary" value="<?= /* @escapeNotVerified */ __('Cancel Coupon') ?>"><span><?= /* @escapeNotVerified */ __('Cancel Coupon') ?></span></button> + <button type="button" class="action cancel primary" value="<?= $block->escapeHtmlAttr(__('Cancel Coupon')) ?>"><span><?= $block->escapeHtml(__('Cancel Coupon')) ?></span></button> </div> <?php endif; ?> </div> </div> - <?php if (!strlen($block->getCouponCode())): ?> - <?= /* @noEscape */ $block->getChildHtml('captcha') ?> + <?php if (!strlen($block->getCouponCode())) : ?> + <?= /* @noEscape */ $block->getChildHtml('captcha') ?> <?php endif; ?> </form> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml index 84ab9b13d8f3a..e1ab036c7d889 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml @@ -4,52 +4,56 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Cart\Grid */ ?> -<?php $mergedCells = ($this->helper('Magento\Tax\Helper\Data')->displayCartBothPrices() ? 2 : 1); ?> +<?php $mergedCells = ($this->helper(Magento\Tax\Helper\Data::class)->displayCartBothPrices() ? 2 : 1); ?> <?= $block->getChildHtml('form_before') ?> -<form action="<?= /* @escapeNotVerified */ $block->getUrl('checkout/cart/updatePost') ?>" +<form action="<?= $block->escapeUrl($block->getUrl('checkout/cart/updatePost')) ?>" method="post" id="form-validate" data-mage-init='{"Magento_Checkout/js/action/update-shopping-cart": - {"validationURL" : "/checkout/cart/updateItemQty", + {"validationURL" : "<?= $block->escapeUrl($block->getUrl('checkout/cart/updateItemQty')) ?>", "updateCartActionContainer": "#update_cart_action_container"} }' class="form form-cart"> <?= $block->getBlockHtml('formkey') ?> <div class="cart table-wrapper<?= $mergedCells == 2 ? ' detailed' : '' ?>"> - <?php if ($block->getPagerHtml()): ?> - <div class="cart-products-toolbar cart-products-toolbar-top toolbar" data-attribute="cart-products-toolbar-top"><?= $block->getPagerHtml() ?></div> + <?php if ($block->getPagerHtml()) :?> + <div class="cart-products-toolbar cart-products-toolbar-top toolbar" + data-attribute="cart-products-toolbar-top"><?= $block->getPagerHtml() ?> + </div> <?php endif ?> <table id="shopping-cart-table" class="cart items data table" data-mage-init='{"shoppingCart":{"emptyCartButton": ".action.clear", "updateCartActionContainer": "#update_cart_action_container"}}'> - <caption class="table-caption"><?= /* @escapeNotVerified */ __('Shopping Cart Items') ?></caption> + <caption class="table-caption"><?= $block->escapeHtml(__('Shopping Cart Items')) ?></caption> <thead> <tr> - <th class="col item" scope="col"><span><?= /* @escapeNotVerified */ __('Item') ?></span></th> - <th class="col price" scope="col"><span><?= /* @escapeNotVerified */ __('Price') ?></span></th> - <th class="col qty" scope="col"><span><?= /* @escapeNotVerified */ __('Qty') ?></span></th> - <th class="col subtotal" scope="col"><span><?= /* @escapeNotVerified */ __('Subtotal') ?></span></th> + <th class="col item" scope="col"><span><?= $block->escapeHtml(__('Item')) ?></span></th> + <th class="col price" scope="col"><span><?= $block->escapeHtml(__('Price')) ?></span></th> + <th class="col qty" scope="col"><span><?= $block->escapeHtml(__('Qty')) ?></span></th> + <th class="col subtotal" scope="col"><span><?= $block->escapeHtml(__('Subtotal')) ?></span></th> </tr> </thead> - <?php foreach ($block->getItems() as $_item): ?> + <?php foreach ($block->getItems() as $_item) :?> <?= $block->getItemHtml($_item) ?> <?php endforeach ?> </table> - <?php if ($block->getPagerHtml()): ?> - <div class="cart-products-toolbar cart-products-toolbar-bottom toolbar" data-attribute="cart-products-toolbar-bottom"><?= $block->getPagerHtml() ?></div> + <?php if ($block->getPagerHtml()) :?> + <div class="cart-products-toolbar cart-products-toolbar-bottom toolbar" + data-attribute="cart-products-toolbar-bottom"><?= $block->getPagerHtml() ?> + </div> <?php endif ?> </div> <div class="cart main actions"> - <?php if ($block->getContinueShoppingUrl()): ?> + <?php if ($block->getContinueShoppingUrl()) :?> <a class="action continue" href="<?= $block->escapeUrl($block->getContinueShoppingUrl()) ?>" title="<?= $block->escapeHtml(__('Continue Shopping')) ?>"> - <span><?= /* @escapeNotVerified */ __('Continue Shopping') ?></span> + <span><?= $block->escapeHtml(__('Continue Shopping')) ?></span> </a> <?php endif; ?> <button type="submit" @@ -58,7 +62,7 @@ value="empty_cart" title="<?= $block->escapeHtml(__('Clear Shopping Cart')) ?>" class="action clear" id="empty_cart_button"> - <span><?= /* @escapeNotVerified */ __('Clear Shopping Cart') ?></span> + <span><?= $block->escapeHtml(__('Clear Shopping Cart')) ?></span> </button> <button type="submit" name="update_cart_action" @@ -66,7 +70,7 @@ value="update_qty" title="<?= $block->escapeHtml(__('Update Shopping Cart')) ?>" class="action update"> - <span><?= /* @escapeNotVerified */ __('Update Shopping Cart') ?></span> + <span><?= $block->escapeHtml(__('Update Shopping Cart')) ?></span> </button> <input type="hidden" value="" id="update_cart_action_container" data-cart-item-update=""/> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml index bfb7ddc55cda6..3b09512eb505b 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml @@ -4,25 +4,23 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Catalog\Block\Product\View */ ?> <?php $_product = $block->getProduct(); ?> <?php $buttonTitle = __('Update Cart'); ?> -<?php if ($_product->isSaleable()): ?> +<?php if ($_product->isSaleable()) :?> <div class="box-tocart update"> <fieldset class="fieldset"> - <?php if ($block->shouldRenderQuantity()): ?> + <?php if ($block->shouldRenderQuantity()) :?> <div class="field qty"> - <label class="label" for="qty"><span><?= /* @escapeNotVerified */ __('Qty') ?></span></label> + <label class="label" for="qty"><span><?= $block->escapeHtml(__('Qty')) ?></span></label> <div class="control"> <input type="number" name="qty" id="qty" min="0" value="" - title="<?= /* @escapeNotVerified */ __('Qty') ?>" + title="<?= $block->escapeHtmlAttr(__('Qty')) ?>" class="input-text qty" data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"/> </div> @@ -30,10 +28,10 @@ <?php endif; ?> <div class="actions"> <button type="submit" - title="<?= /* @escapeNotVerified */ $buttonTitle ?>" + title="<?= $block->escapeHtmlAttr($buttonTitle) ?>" class="action primary tocart" id="product-updatecart-button"> - <span><?= /* @escapeNotVerified */ $buttonTitle ?></span> + <span><?= $block->escapeHtml($buttonTitle) ?></span> </button> <?= $block->getChildHtml('', true) ?> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index d15794fb761bb..77dde1eab482a 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -4,7 +4,8 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate +// phpcs:disable Magento2.Files.LineLength.MaxExceeded /** @var $block \Magento\Checkout\Block\Cart\Item\Renderer */ @@ -12,72 +13,82 @@ $_item = $block->getItem(); $product = $_item->getProduct(); $isVisibleProduct = $product->isVisibleInSiteVisibility(); /** @var \Magento\Msrp\Helper\Data $helper */ -$helper = $this->helper('Magento\Msrp\Helper\Data'); +$helper = $this->helper(Magento\Msrp\Helper\Data::class); $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinimalPriceLessMsrp($product); ?> <tbody class="cart item"> <tr class="item-info"> <td data-th="<?= $block->escapeHtml(__('Item')) ?>" class="col item"> - <?php if ($block->hasProductUrl()):?> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl() ?>" + <?php if ($block->hasProductUrl()) :?> + <a href="<?= $block->escapeUrl($block->getProductUrl()) ?>" title="<?= $block->escapeHtml($block->getProductName()) ?>" tabindex="-1" class="product-item-photo"> - <?php else:?> + <?php else :?> <span class="product-item-photo"> <?php endif;?> <?= $block->getImage($block->getProductForThumbnail(), 'cart_page_product_thumbnail')->toHtml() ?> - <?php if ($block->hasProductUrl()):?> + <?php if ($block->hasProductUrl()) :?> </a> - <?php else: ?> + <?php else :?> </span> <?php endif; ?> <div class="product-item-details"> <strong class="product-item-name"> - <?php if ($block->hasProductUrl()):?> - <a href="<?= /* @escapeNotVerified */ $block->getProductUrl() ?>"><?= $block->escapeHtml($block->getProductName()) ?></a> - <?php else: ?> + <?php if ($block->hasProductUrl()) :?> + <a href="<?= $block->escapeUrl($block->getProductUrl()) ?>"><?= $block->escapeHtml($block->getProductName()) ?></a> + <?php else :?> <?= $block->escapeHtml($block->getProductName()) ?> <?php endif; ?> </strong> - <?php if ($_options = $block->getOptionList()):?> + <?php if ($_options = $block->getOptionList()) :?> <dl class="item-options"> - <?php foreach ($_options as $_option) : ?> + <?php foreach ($_options as $_option) :?> <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> <dt><?= $block->escapeHtml($_option['label']) ?></dt> <dd> - <?php if (isset($_formatedOptionValue['full_view'])): ?> - <?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?> - <?php else: ?> + <?php if (isset($_formatedOptionValue['full_view'])) :?> + <?= $block->escapeHtml($_formatedOptionValue['full_view']) ?> + <?php else :?> <?= $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?> <?php endif; ?> </dd> <?php endforeach; ?> </dl> <?php endif;?> - <?php if ($messages = $block->getMessages()): ?> - <?php foreach ($messages as $message): ?> - <div class="cart item message <?= /* @escapeNotVerified */ $message['type'] ?>"><div><?= $block->escapeHtml($message['text']) ?></div></div> + <?php if ($messages = $block->getMessages()) :?> + <?php foreach ($messages as $message) :?> + <div class= "cart item message <?= $block->escapeHtmlAttr($message['type']) ?>"> + <div><?= $block->escapeHtml($message['text']) ?></div> + </div> <?php endforeach; ?> <?php endif; ?> <?php $addInfoBlock = $block->getProductAdditionalInformationBlock(); ?> - <?php if ($addInfoBlock): ?> + <?php if ($addInfoBlock) :?> <?= $addInfoBlock->setItem($_item)->toHtml() ?> <?php endif;?> </div> </td> - <?php if ($canApplyMsrp): ?> + <?php if ($canApplyMsrp) :?> <td class="col msrp" data-th="<?= $block->escapeHtml(__('Price')) ?>"> <span class="pricing msrp"> - <span class="msrp notice"><?= /* @escapeNotVerified */ __('See price before order confirmation.') ?></span> + <span class="msrp notice"><?= $block->escapeHtml(__('See price before order confirmation.')) ?></span> <?php $helpLinkId = 'cart-msrp-help-' . $_item->getId(); ?> - <a href="#" class="action help map" id="<?= /* @escapeNotVerified */ ($helpLinkId) ?>" data-mage-init='{"addToCart":{"helpLinkId": "#<?= /* @escapeNotVerified */ $helpLinkId ?>","productName": "<?= /* @escapeNotVerified */ $product->getName() ?>","showAddToCart": false}}'> - <span><?= /* @escapeNotVerified */ __("What's this?") ?></span> + <a href="#" class="action help map" + id="<?= ($block->escapeHtmlAttr($helpLinkId)) ?>" + data-mage-init='{"addToCart":{ + "helpLinkId": "#<?= $block->escapeJs($block->escapeHtml($helpLinkId)) ?>", + "productName": "<?= $block->escapeJs($block->escapeHtml($product->getName())) ?>", + "showAddToCart": false + } + }' + > + <span><?= $block->escapeHtml(__("What's this?")) ?></span> </a> </span> </td> - <?php else: ?> + <?php else :?> <td class="col price" data-th="<?= $block->escapeHtml(__('Price')) ?>"> <?= $block->getUnitPriceHtml($_item) ?> </td> @@ -85,15 +96,15 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty')) ?>"> <div class="field qty"> <div class="control qty"> - <label for="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty"> - <span class="label"><?= /* @escapeNotVerified */ __('Qty') ?></span> - <input id="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty" - name="cart[<?= /* @escapeNotVerified */ $_item->getId() ?>][qty]" - data-cart-item-id="<?= $block->escapeHtml($_item->getSku()) ?>" - value="<?= /* @escapeNotVerified */ $block->getQty() ?>" + <label for="cart-<?= $block->escapeHtmlAttr($_item->getId()) ?>-qty"> + <span class="label"><?= $block->escapeHtml(__('Qty')) ?></span> + <input id="cart-<?= $block->escapeHtmlAttr($_item->getId()) ?>-qty" + name="cart[<?= $block->escapeHtmlAttr($_item->getId()) ?>][qty]" + data-cart-item-id="<?= $block->escapeHtmlAttr($_item->getSku()) ?>" + value="<?= $block->escapeHtmlAttr($block->getQty()) ?>" type="number" size="4" - title="<?= $block->escapeHtml(__('Qty')) ?>" + title="<?= $block->escapeHtmlAttr(__('Qty')) ?>" class="input-text qty" data-validate="{required:true,'validate-greater-than-zero':true}" data-role="cart-item-qty"/> @@ -103,9 +114,9 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima </td> <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if ($canApplyMsrp): ?> + <?php if ($canApplyMsrp) :?> <span class="cart msrp subtotal">--</span> - <?php else: ?> + <?php else :?> <?= $block->getRowTotalHtml($_item) ?> <?php endif; ?> </td> @@ -113,7 +124,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <tr class="item-actions"> <td colspan="4"> <div class="actions-toolbar"> - <?= /* @escapeNotVerified */ $block->getActions($_item) ?> + <?= /* @noEscape */ $block->getActions($_item) ?> </div> </td> </tr> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/price/sidebar.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/price/sidebar.phtml index d7a625695b476..1d30cfc8dc45d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/price/sidebar.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/price/sidebar.phtml @@ -4,12 +4,17 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ ?> <?php $_item = $block->getItem() ?> <div class="price-container"> - <span class="price-label"><?= /* @escapeNotVerified */ __('Price') ?></span> - <span class="price-wrapper"><?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_item->getCalculationPrice()) ?></span> + <span class="price-label"><?= $block->escapeHtml(__('Price')) ?></span> + <span class="price-wrapper"> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_item->getCalculationPrice()), + ['span'] + ) ?> + </span> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml index da0a83f05ef60..357bbf27772b5 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Checkout\Block\Cart\Item\Renderer\Actions\Edit */ ?> -<?php if ($block->isProductVisibleInSiteVisibility()): ?> +<?php if ($block->isProductVisibleInSiteVisibility()) :?> <a class="action action-edit" - href="<?= /* @escapeNotVerified */ $block->getConfigureUrl() ?>" - title="<?= $block->escapeHtml(__('Edit item parameters')) ?>"> - <span><?= /* @escapeNotVerified */ __('Edit') ?></span> + href="<?= $block->escapeUrl($block->getConfigureUrl()) ?>" + title="<?= $block->escapeHtmlAttr(__('Edit item parameters')) ?>"> + <span><?= $block->escapeHtml(__('Edit')) ?></span> </a> <?php endif ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/remove.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/remove.phtml index 445721ca5d0c2..8b72b6c55e821 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/remove.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/remove.phtml @@ -4,15 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Checkout\Block\Cart\Item\Renderer\Actions\Remove */ ?> <a href="#" title="<?= $block->escapeHtml(__('Remove item')) ?>" class="action action-delete" - data-post='<?= /* @escapeNotVerified */ $block->getDeletePostJson() ?>'> + data-post='<?= /* @noEscape */ $block->getDeletePostJson() ?>'> <span> - <?= /* @escapeNotVerified */ __('Remove item') ?> + <?= $block->escapeHtml(__('Remove item')) ?> </span> </a> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml index d329e2e8c1770..b045f4ec98ff7 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml @@ -4,20 +4,18 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Checkout\Block\Cart */ ?> -<?php if (!$block->hasError()): ?> -<?php $methods = $block->getMethods('methods') ?: $block->getMethods('top_methods') ?> -<ul class="checkout methods items checkout-methods-items"> -<?php foreach ($methods as $method): ?> - <?php $methodHtml = $block->getMethodHtml($method); ?> - <?php if (trim($methodHtml) !== ''): ?> - <li class="item"><?= /* @escapeNotVerified */ $methodHtml ?></li> - <?php endif; ?> -<?php endforeach; ?> -</ul> +<?php if (!$block->hasError()) :?> + <?php $methods = $block->getMethods('methods') ?: $block->getMethods('top_methods') ?> + <ul class="checkout methods items checkout-methods-items"> + <?php foreach ($methods as $method) :?> + <?php $methodHtml = $block->getMethodHtml($method); ?> + <?php if (trim($methodHtml) !== '') :?> + <li class="item"><?= /* @noEscape */ $methodHtml ?></li> + <?php endif; ?> + <?php endforeach; ?> + </ul> <?php endif; ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml index 20be9cd010c64..28275e0223936 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml @@ -4,17 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Checkout\Block\Cart\Sidebar */ ?> <div data-block="minicart" class="minicart-wrapper"> - <a class="action showcart" href="<?= /* @escapeNotVerified */ $block->getShoppingCartUrl() ?>" + <a class="action showcart" href="<?= $block->escapeUrl($block->getShoppingCartUrl()) ?>" data-bind="scope: 'minicart_content'"> - <span class="text"><?= /* @escapeNotVerified */ __('My Cart') ?></span> + <span class="text"><?= $block->escapeHtml(__('My Cart')) ?></span> <span class="counter qty empty" - data-bind="css: { empty: !!getCartParam('summary_count') == false }, blockLoader: isLoading"> + data-bind="css: { empty: !!getCartParam('summary_count') == false && !isLoading() }, blockLoader: isLoading"> <span class="counter-number"><!-- ko text: getCartParam('summary_count') --><!-- /ko --></span> <span class="counter-label"> <!-- ko if: getCartParam('summary_count') --> @@ -24,7 +22,7 @@ </span> </span> </a> - <?php if ($block->getIsNeedToDisplaySideBar()): ?> + <?php if ($block->getIsNeedToDisplaySideBar()) :?> <div class="block block-minicart" data-role="dropdownDialog" data-mage-init='{"dropdownDialog":{ @@ -41,7 +39,7 @@ </div> <?= $block->getChildHtml('minicart.addons') ?> </div> - <?php else: ?> + <?php else :?> <script> require(['jquery'], function ($) { $('a.action.showcart').click(function() { @@ -51,15 +49,17 @@ </script> <?php endif ?> <script> - window.checkout = <?= /* @escapeNotVerified */ $block->getSerializedConfig() ?>; + window.checkout = <?= /* @noEscape */ $block->getSerializedConfig() ?>; </script> <script type="text/x-magento-init"> { "[data-block='minicart']": { - "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?> + "Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?> }, "*": { - "Magento_Ui/js/block-loader": "<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>" + "Magento_Ui/js/block-loader": "<?= $block->escapeJs( + $block->escapeUrl($block->getViewFileUrl('images/loader-1.gif')) + ) ?>" } } </script> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/noItems.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/noItems.phtml index 67ac4a9335565..ac150b2aa98b9 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/noItems.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/noItems.phtml @@ -3,14 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + /** @var $block \Magento\Checkout\Block\Cart */ ?> <div class="cart-empty"> <?= $block->getChildHtml('checkout_cart_empty_widget') ?> - <p><?= /* @escapeNotVerified */ __('You have no items in your shopping cart.') ?></p> - <p><?php /* @escapeNotVerified */ echo __('Click <a href="%1">here</a> to continue shopping.', - $block->escapeUrl($block->getContinueShoppingUrl())) ?></p> + <p><?= $block->escapeHtml(__('You have no items in your shopping cart.')) ?></p> + <p><?= $block->escapeHtml( + __( + 'Click <a href="%1">here</a> to continue shopping.', + $block->escapeUrl($block->getContinueShoppingUrl()) + ), + ['a'] + ) ?> + </p> <?= $block->getChildHtml('shopping.cart.table.after') ?> </div> <script type="text/x-magento-init"> @@ -19,4 +25,4 @@ "Magento_Checkout/js/empty-cart": {} } } -</script> \ No newline at end of file +</script> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/shipping.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/shipping.phtml index b5ddb8446ba05..a44d37dccfdc5 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/shipping.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/shipping.phtml @@ -4,36 +4,47 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Checkout\Block\Cart\Shipping */ ?> -<div id="block-shipping" class="block shipping" data-mage-init='{"collapsible":{"openedState": "active", "saveState": true}}'> +<div id="block-shipping" + class="block shipping" + data-mage-init='{"collapsible":{"openedState": "active", "saveState": true}}' +> <div class="title" data-role="title"> <strong id="block-shipping-heading" role="heading" aria-level="2"> - <?= /* @escapeNotVerified */ $block->getQuote()->isVirtual() ? __('Estimate Tax') : __('Estimate Shipping and Tax') ?> + <?= $block->getQuote()->isVirtual() + ? $block->escapeHtml(__('Estimate Tax')) + : $block->escapeHtml(__('Estimate Shipping and Tax')) + ?> </strong> </div> - <div id="block-summary" data-bind="scope:'block-summary'" class="content" data-role="content" aria-labelledby="block-shipping-heading"> + <div id="block-summary" + data-bind="scope:'block-summary'" + class="content" + data-role="content" + aria-labelledby="block-shipping-heading" + > <!-- ko template: getTemplate() --><!-- /ko --> <script type="text/x-magento-init"> { "#block-summary": { - "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?> + "Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?> } } </script> <script> - window.checkoutConfig = <?= /* @escapeNotVerified */ $block->getSerializedCheckoutConfig() ?>; + window.checkoutConfig = <?= /* @noEscape */ $block->getSerializedCheckoutConfig() ?>; window.customerData = window.checkoutConfig.customerData; window.isCustomerLoggedIn = window.checkoutConfig.isCustomerLoggedIn; require([ 'mage/url', 'Magento_Ui/js/block-loader' ], function(url, blockLoader) { - blockLoader("<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>"); - return url.setBaseUrl('<?= /* @escapeNotVerified */ $block->getBaseUrl() ?>'); + blockLoader( + "<?= $block->escapeJs($block->escapeUrl($block->getViewFileUrl('images/loader-1.gif'))) ?>" + ); + return url.setBaseUrl('<?= $block->escapeJs($block->escapeUrl($block->getBaseUrl())) ?>'); }) </script> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/totals.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/totals.phtml index f39b70df98424..784c4c39076e6 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/totals.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/totals.phtml @@ -15,7 +15,7 @@ <script type="text/x-magento-init"> { "#cart-totals": { - "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?> + "Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?> } } </script> diff --git a/app/code/Magento/Checkout/view/frontend/templates/item/price/row.phtml b/app/code/Magento/Checkout/view/frontend/templates/item/price/row.phtml index 37a945b238d30..533d75b6ae843 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/item/price/row.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/item/price/row.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ @@ -12,6 +12,9 @@ $_item = $block->getItem(); ?> <span class="price-excluding-tax" data-label="<?= $block->escapeHtml(__('Excl. Tax')) ?>"> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_item->getRowTotal()) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_item->getRowTotal()), + ['span'] + ) ?> </span> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/item/price/unit.phtml b/app/code/Magento/Checkout/view/frontend/templates/item/price/unit.phtml index 45a6ef48e36d6..fafbe9c7c969d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/item/price/unit.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/item/price/unit.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ @@ -12,6 +12,9 @@ $_item = $block->getItem(); ?> <span class="price-including-tax" data-label="<?= $block->escapeHtml(__('Excl. Tax')) ?>"> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_item->getCalculationPrice()) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_item->getCalculationPrice()), + ['span'] + ) ?> </span> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/js/components.phtml b/app/code/Magento/Checkout/view/frontend/templates/js/components.phtml index bad5acc209b5f..6cf15f4770150 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/js/components.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/js/components.phtml @@ -4,7 +4,5 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml() ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/messages/addCartSuccessMessage.phtml b/app/code/Magento/Checkout/view/frontend/templates/messages/addCartSuccessMessage.phtml index e835037b5fcb4..a6686444d2ed5 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/messages/addCartSuccessMessage.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/messages/addCartSuccessMessage.phtml @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile + /** @var \Magento\Framework\View\Element\Template $block */ ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml index 47a56e8f333bc..55f7039f33344 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +/** @var $block \Magento\Checkout\Block\Onepage */ ?> <div id="checkout" data-bind="scope:'checkout'" class="checkout-container"> <div id="checkout-loader" data-role="checkout-loader" class="loading-mask" data-mage-init='{"checkoutLoader": {}}'> <div class="loader"> - <img src="<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>" - alt="<?= /* @escapeNotVerified */ __('Loading...') ?>" + <img src="<?= $block->escapeUrl($block->getViewFileUrl('images/loader-1.gif')) ?>" + alt="<?= $block->escapeHtmlAttr(__('Loading...')) ?>" style="position: absolute;"> </div> </div> @@ -18,12 +18,12 @@ <script type="text/x-magento-init"> { "#checkout": { - "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?> + "Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?> } } </script> <script> - window.checkoutConfig = <?= /* @escapeNotVerified */ $block->getSerializedCheckoutConfig() ?>; + window.checkoutConfig = <?= /* @noEscape */ $block->getSerializedCheckoutConfig() ?>; // Create aliases for customer.js model from customer module window.isCustomerLoggedIn = window.checkoutConfig.isCustomerLoggedIn; window.customerData = window.checkoutConfig.customerData; @@ -33,8 +33,8 @@ 'mage/url', 'Magento_Ui/js/block-loader' ], function(url, blockLoader) { - blockLoader("<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>"); - return url.setBaseUrl('<?= /* @escapeNotVerified */ $block->getBaseUrl() ?>'); + blockLoader("<?= $block->escapeJs($block->escapeUrl($block->getViewFileUrl('images/loader-1.gif'))) ?>"); + return url.setBaseUrl('<?= $block->escapeJs($block->escapeUrl($block->getBaseUrl())) ?>'); }) </script> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/failure.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/failure.phtml index 43791ef496745..ace2b0464171c 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/failure.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/failure.phtml @@ -4,9 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - +/** @var $block \Magento\Checkout\Block\Onepage\Failure */ ?> -<?php if ($block->getRealOrderId()) : ?><p><?= /* @escapeNotVerified */ __('Order #') . $block->getRealOrderId() ?></p><?php endif ?> -<?php if ($error = $block->getErrorMessage()) : ?><p><?= /* @escapeNotVerified */ $error ?></p><?php endif ?> -<p><?= /* @escapeNotVerified */ __('Click <a href="%1">here</a> to continue shopping.', $block->escapeUrl($block->getContinueShoppingUrl())) ?></p> +<?php if ($block->getRealOrderId()) :?> + <p><?= $block->escapeHtml(__('Order #') . $block->getRealOrderId()) ?></p> +<?php endif ?> +<?php if ($error = $block->getErrorMessage()) :?> + <p><?= $block->escapeHtml($error) ?></p> +<?php endif ?> +<p><?= $block->escapeHtml( + __('Click <a href="%1">here</a> to continue shopping.', $block->escapeUrl($block->getContinueShoppingUrl())), + ['a'] +) ?> +</p> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/link.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/link.phtml index 53a1fe8783509..b667764ac7bba 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/link.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/link.phtml @@ -4,15 +4,21 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +/** @var $block \Magento\Checkout\Block\Onepage\Link */ ?> -<?php if ($block->isPossibleOnepageCheckout()):?> +<?php if ($block->isPossibleOnepageCheckout()) :?> <button type="button" data-role="proceed-to-checkout" - title="<?= /* @escapeNotVerified */ __('Proceed to Checkout') ?>" - data-mage-init='{"Magento_Checkout/js/proceed-to-checkout":{"checkoutUrl":"<?= /* @escapeNotVerified */ $block->getCheckoutUrl() ?>"}}' + title="<?= $block->escapeHtmlAttr(__('Proceed to Checkout')) ?>" + data-mage-init='{ + "Magento_Checkout/js/proceed-to-checkout":{ + "checkoutUrl":"<?= $block->escapeJs($block->escapeUrl($block->getCheckoutUrl())) ?>" + } + }' class="action primary checkout<?= ($block->isDisabled()) ? ' disabled' : '' ?>" - <?php if ($block->isDisabled()):?>disabled="disabled"<?php endif; ?>> - <span><?= /* @escapeNotVerified */ __('Proceed to Checkout') ?></span> + <?php if ($block->isDisabled()) :?> + disabled="disabled" + <?php endif; ?>> + <span><?= $block->escapeHtml(__('Proceed to Checkout')) ?></span> </button> <?php endif?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item.phtml index 2428cc010779d..b3b94cfae18dc 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item.phtml @@ -4,11 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block Magento\Checkout\Block\Cart\Item\Renderer */ $_item = $block->getItem(); +$taxDataHelper = $this->helper(Magento\Tax\Helper\Data::class); ?> <tbody class="cart item"> <tr> @@ -17,47 +18,53 @@ $_item = $block->getItem(); <?= $block->getImage($block->getProductForThumbnail(), 'cart_page_product_thumbnail')->toHtml() ?> </span> <div class="product-item-details"> - <strong class="product name product-item-name"><?= $block->escapeHtml($block->getProductName()) ?></strong> - <?php if ($_options = $block->getOptionList()):?> - <dl class="item-options"> - <?php foreach ($_options as $_option) : ?> - <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> - <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <dd> - <?php if (isset($_formatedOptionValue['full_view'])): ?> - <?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?> - <?php else: ?> - <?= /* @escapeNotVerified */ $_formatedOptionValue['value'] ?> - <?php endif; ?> - </dd> - <?php endforeach; ?> - </dl> + <strong class="product name product-item-name"> + <?= $block->escapeHtml($block->getProductName()) ?> + </strong> + <?php if ($_options = $block->getOptionList()) :?> + <dl class="item-options"> + <?php foreach ($_options as $_option) :?> + <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> + <dt><?= $block->escapeHtml($_option['label']) ?></dt> + <dd> + <?php if (isset($_formatedOptionValue['full_view'])) :?> + <?= /* @noEscape */ $_formatedOptionValue['full_view'] ?> + <?php else :?> + <?= /* @noEscape */ $_formatedOptionValue['value'] ?> + <?php endif; ?> + </dd> + <?php endforeach; ?> + </dl> <?php endif;?> - <?php if ($addtInfoBlock = $block->getProductAdditionalInformationBlock()):?> + <?php if ($addtInfoBlock = $block->getProductAdditionalInformationBlock()) :?> <?= $addtInfoBlock->setItem($_item)->toHtml() ?> <?php endif;?> </div> </td> <td class="col price" data-th="<?= $block->escapeHtml(__('Price')) ?>"> - <?php if ($this->helper('Magento\Tax\Helper\Data')->displayCartPriceInclTax() || $this->helper('Magento\Tax\Helper\Data')->displayCartBothPrices()): ?> + <?php if ($taxDataHelper->displayCartPriceInclTax() || $taxDataHelper->displayCartBothPrices()) :?> <span class="price-including-tax" data-label="<?= $block->escapeHtml(__('Incl. Tax')) ?>"> <?= $block->getUnitPriceInclTaxHtml($_item) ?> </span> <?php endif; ?> - <?php if ($this->helper('Magento\Tax\Helper\Data')->displayCartPriceExclTax() || $this->helper('Magento\Tax\Helper\Data')->displayCartBothPrices()): ?> + <?php if ($taxDataHelper->displayCartPriceExclTax() || $taxDataHelper->displayCartBothPrices()) :?> <span class="price-excluding-tax" data-label="<?= $block->escapeHtml(__('Excl. Tax')) ?>"> <?= $block->getUnitPriceExclTaxHtml($_item) ?> </span> <?php endif; ?> </td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty')) ?>"><span class="qty"><?= /* @escapeNotVerified */ $_item->getQty() ?></span></td> + <td class="col qty" + data-th="<?= $block->escapeHtml(__('Qty')) ?>" + > + <span class="qty"><?= $block->escapeHtml($_item->getQty()) ?></span> + </td> <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if ($this->helper('Magento\Tax\Helper\Data')->displayCartPriceInclTax() || $this->helper('Magento\Tax\Helper\Data')->displayCartBothPrices()): ?> + <?php if ($taxDataHelper->displayCartPriceInclTax() || $taxDataHelper->displayCartBothPrices()) :?> <span class="price-including-tax" data-label="<?= $block->escapeHtml(__('Incl. Tax')) ?>"> <?= $block->getRowTotalInclTaxHtml($_item) ?> </span> <?php endif; ?> - <?php if ($this->helper('Magento\Tax\Helper\Data')->displayCartPriceExclTax() || $this->helper('Magento\Tax\Helper\Data')->displayCartBothPrices()): ?> + <?php if ($taxDataHelper->displayCartPriceExclTax() || $taxDataHelper->displayCartBothPrices()) :?> <span class="price-excluding-tax" data-label="<?= $block->escapeHtml(__('Excl. Tax')) ?>"> <?= $block->getRowTotalExclTaxHtml($_item) ?> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_excl_tax.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_excl_tax.phtml index 7ee3e416b9ade..64eefd0dad011 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_excl_tax.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_excl_tax.phtml @@ -4,12 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ $_item = $block->getItem(); ?> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_item->getRowTotal()) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_item->getRowTotal()), + ['span'] + ) ?> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_incl_tax.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_incl_tax.phtml index 2f364aafbbcc0..14deb07640e6d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_incl_tax.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/row_incl_tax.phtml @@ -4,13 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ $_item = $block->getItem(); ?> -<?php $_incl = $this->helper('Magento\Checkout\Helper\Data')->getSubtotalInclTax($_item); ?> +<?php $_incl = $this->helper(Magento\Checkout\Helper\Data::class)->getSubtotalInclTax($_item); ?> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_incl) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_incl), + ['span'] + ) ?> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_excl_tax.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_excl_tax.phtml index a1ec004c2a886..a65a5a50ae1a1 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_excl_tax.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_excl_tax.phtml @@ -4,12 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ $_item = $block->getItem(); ?> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_item->getCalculationPrice()) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_item->getCalculationPrice()), + ['span'] + ) ?> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_incl_tax.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_incl_tax.phtml index 0ed3c05ee6d1f..b623e1f1c3fd9 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_incl_tax.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage/review/item/price/unit_incl_tax.phtml @@ -4,13 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate /** @var $block \Magento\Checkout\Block\Item\Price\Renderer */ $_item = $block->getItem(); ?> -<?php $_incl = $this->helper('Magento\Checkout\Helper\Data')->getPriceInclTax($_item); ?> +<?php $_incl = $this->helper(Magento\Checkout\Helper\Data::class)->getPriceInclTax($_item); ?> <span class="cart-price"> - <?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($_incl) ?> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($_incl), + ['span'] + ) ?> </span> diff --git a/app/code/Magento/Checkout/view/frontend/templates/registration.phtml b/app/code/Magento/Checkout/view/frontend/templates/registration.phtml index f239fbd47dec2..da36b4b61d656 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/registration.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/registration.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +/** @var $block \Magento\Checkout\Block\Registration */ ?> <div id="registration" data-bind="scope:'registration'"> <br /> @@ -17,8 +17,9 @@ "registration": { "component": "Magento_Checkout/js/view/registration", "config": { - "registrationUrl": "<?= /* @escapeNotVerified */ $block->getCreateAccountUrl() ?>", - "email": "<?= /* @escapeNotVerified */ $block->getEmailAddress() ?>" + "registrationUrl": + "<?= $block->escapeJs($block->escapeUrl($block->getCreateAccountUrl())) ?>", + "email": "<?= $block->escapeJs($block->getEmailAddress()) ?>" }, "children": { "errors": { diff --git a/app/code/Magento/Checkout/view/frontend/templates/shipping/price.phtml b/app/code/Magento/Checkout/view/frontend/templates/shipping/price.phtml index 892b7926525f3..ba1a7a20376b3 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/shipping/price.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/shipping/price.phtml @@ -4,10 +4,8 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Checkout\Block\Shipping\Price */ ?> <?php $shippingPrice = $block->getShippingPrice(); ?> -<?= /* @escapeNotVerified */ $shippingPrice ?> +<?= $block->escapeHtml($shippingPrice) ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/success.phtml b/app/code/Magento/Checkout/view/frontend/templates/success.phtml index b3517eab8a5d3..828b4eb86c330 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/success.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/success.phtml @@ -4,25 +4,23 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <?php /** @var $block \Magento\Checkout\Block\Onepage\Success */ ?> <div class="checkout-success"> - <?php if ($block->getOrderId()):?> + <?php if ($block->getOrderId()) :?> <?php if ($block->getCanViewOrder()) :?> - <p><?= __('Your order number is: %1.', sprintf('<a href="%s" class="order-number"><strong>%s</strong></a>', $block->escapeHtml($block->getViewOrderUrl()), $block->escapeHtml($block->getOrderId()))) ?></p> + <p><?= $block->escapeHtml(__('Your order number is: %1.', sprintf('<a href="%s" class="order-number"><strong>%s</strong></a>', $block->escapeUrl($block->getViewOrderUrl()), $block->getOrderId())), ['a', 'strong']) ?></p> <?php else :?> - <p><?= __('Your order # is: <span>%1</span>.', $block->escapeHtml($block->getOrderId())) ?></p> + <p><?= $block->escapeHtml(__('Your order # is: <span>%1</span>.', $block->getOrderId()), ['span']) ?></p> <?php endif;?> - <p><?= /* @escapeNotVerified */ __('We\'ll email you an order confirmation with details and tracking info.') ?></p> + <p><?= $block->escapeHtml(__('We\'ll email you an order confirmation with details and tracking info.')) ?></p> <?php endif;?> <?= $block->getAdditionalInfoHtml() ?> <div class="actions-toolbar"> <div class="primary"> - <a class="action primary continue" href="<?= /* @escapeNotVerified */ $block->getContinueUrl() ?>"><span><?= /* @escapeNotVerified */ __('Continue Shopping') ?></span></a> + <a class="action primary continue" href="<?= $block->escapeUrl($block->getContinueUrl()) ?>"><span><?= $block->escapeHtml(__('Continue Shopping')) ?></span></a> </div> </div> </div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/total/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/total/default.phtml index 2ea1cdd7f53f5..0d9da171c11a8 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/total/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/total/default.phtml @@ -4,18 +4,40 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Templates.ThisInTemplate +/** @var $block \Magento\Checkout\Block\Total\DefaultTotal */ ?> <tr class="totals"> - <th colspan="<?= /* @escapeNotVerified */ $block->getColspan() ?>" style="<?= /* @escapeNotVerified */ $block->getTotal()->getStyle() ?>" class="mark" scope="row"> - <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()): ?><strong><?php endif; ?> - <?= $block->escapeHtml($block->getTotal()->getTitle()) ?> - <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()): ?></strong><?php endif; ?> + <th + colspan="<?= $block->escapeHtmlAttr($block->getColspan()) ?>" + style="<?= $block->escapeHtmlAttr($block->getTotal()->getStyle()) ?>" + class="mark" scope="row" + > + <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()) :?> + <strong> + <?php endif; ?> + <?= $block->escapeHtml($block->getTotal()->getTitle()) ?> + <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()) :?> + </strong> + <?php endif; ?> </th> - <td style="<?= /* @escapeNotVerified */ $block->getTotal()->getStyle() ?>" class="amount" data-th="<?= $block->escapeHtml($block->getTotal()->getTitle()) ?>"> - <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()): ?><strong><?php endif; ?> - <span><?= /* @escapeNotVerified */ $this->helper('Magento\Checkout\Helper\Data')->formatPrice($block->getTotal()->getValue()) ?></span> - <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()): ?></strong><?php endif; ?> + <td + style="<?= $block->escapeHtmlAttr($block->getTotal()->getStyle()) ?>" + class="amount" + data-th="<?= $block->escapeHtmlAttr($block->getTotal()->getTitle()) ?>" + > + <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()) :?> + <strong> + <?php endif; ?> + <span> + <?= $block->escapeHtml( + $this->helper(Magento\Checkout\Helper\Data::class)->formatPrice($block->getTotal()->getValue()), + ['span'] + ) ?> + </span> + <?php if ($block->getRenderingArea() == $block->getTotal()->getArea()) :?> + </strong> + <?php endif; ?> </td> </tr> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js index 702df47526715..34f1700749794 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js @@ -12,6 +12,11 @@ define([ 'use strict'; return function (paymentMethod) { + if (paymentMethod) { + paymentMethod.__disableTmpl = { + title: true + }; + } quote.paymentMethod(paymentMethod); }; }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js new file mode 100644 index 0000000000000..9de8a93905c99 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information-extended.js @@ -0,0 +1,79 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/model/url-builder', + 'mage/storage', + 'Magento_Checkout/js/model/error-processor', + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/action/get-totals', + 'Magento_Checkout/js/model/full-screen-loader', + 'underscore' +], function (quote, urlBuilder, storage, errorProcessor, customer, getTotalsAction, fullScreenLoader, _) { + 'use strict'; + + /** + * Filter template data. + * + * @param {Object|Array} data + */ + var filterTemplateData = function (data) { + return _.each(data, function (value, key, list) { + if (_.isArray(value) || _.isObject(value)) { + list[key] = filterTemplateData(value); + } + + if (key === '__disableTmpl') { + delete list[key]; + } + }); + }; + + return function (messageContainer, paymentData, skipBilling) { + var serviceUrl, + payload; + + paymentData = filterTemplateData(paymentData); + skipBilling = skipBilling || false; + payload = { + cartId: quote.getQuoteId(), + paymentMethod: paymentData + }; + + /** + * Checkout for guest and registered customer. + */ + if (!customer.isLoggedIn()) { + serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/set-payment-information', { + cartId: quote.getQuoteId() + }); + payload.email = quote.guestEmail; + } else { + serviceUrl = urlBuilder.createUrl('/carts/mine/set-payment-information', {}); + } + + if (skipBilling === false) { + payload.billingAddress = quote.billingAddress(); + } + + fullScreenLoader.startLoader(); + + return storage.post( + serviceUrl, JSON.stringify(payload) + ).fail( + function (response) { + errorProcessor.process(response, messageContainer); + } + ).always( + function () { + fullScreenLoader.stopLoader(); + } + ); + }; +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js index 997b60503a2b3..d5261c976a725 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js @@ -7,54 +7,13 @@ * @api */ define([ - 'Magento_Checkout/js/model/quote', - 'Magento_Checkout/js/model/url-builder', - 'mage/storage', - 'Magento_Checkout/js/model/error-processor', - 'Magento_Customer/js/model/customer', - 'Magento_Checkout/js/action/get-totals', - 'Magento_Checkout/js/model/full-screen-loader' -], function (quote, urlBuilder, storage, errorProcessor, customer, getTotalsAction, fullScreenLoader) { + 'Magento_Checkout/js/action/set-payment-information-extended' + +], function (setPaymentInformationExtended) { 'use strict'; return function (messageContainer, paymentData) { - var serviceUrl, - payload; - - /** - * Checkout for guest and registered customer. - */ - if (!customer.isLoggedIn()) { - serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/set-payment-information', { - cartId: quote.getQuoteId() - }); - payload = { - cartId: quote.getQuoteId(), - email: quote.guestEmail, - paymentMethod: paymentData, - billingAddress: quote.billingAddress() - }; - } else { - serviceUrl = urlBuilder.createUrl('/carts/mine/set-payment-information', {}); - payload = { - cartId: quote.getQuoteId(), - paymentMethod: paymentData, - billingAddress: quote.billingAddress() - }; - } - - fullScreenLoader.startLoader(); - return storage.post( - serviceUrl, JSON.stringify(payload) - ).fail( - function (response) { - errorProcessor.process(response, messageContainer); - } - ).always( - function () { - fullScreenLoader.stopLoader(); - } - ); + return setPaymentInformationExtended(messageContainer, paymentData, false); }; }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js index 1920bc4d7ac41..a8e70b65019ce 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js @@ -6,7 +6,7 @@ define([ 'Magento_Ui/js/modal/alert', 'jquery', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/validation' ], function (alert, $) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/discount-codes.js b/app/code/Magento/Checkout/view/frontend/web/js/discount-codes.js index db6e8fef24455..1f38ff28a860c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/discount-codes.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/discount-codes.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/billing-address-postcode-validator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/billing-address-postcode-validator.js new file mode 100644 index 0000000000000..b101b67d26bae --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/billing-address-postcode-validator.js @@ -0,0 +1,82 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Checkout/js/model/postcode-validator', + 'mage/translate', + 'uiRegistry' + ], function ( + $, + postcodeValidator, + $t, + uiRegistry +) { + 'use strict'; + + var postcodeElementName = 'postcode'; + + return { + validateZipCodeTimeout: 0, + validateDelay: 2000, + + /** + * Perform postponed binding for fieldset elements + * + * @param {String} formPath + */ + initFields: function (formPath) { + var self = this; + + uiRegistry.async(formPath + '.' + postcodeElementName)(self.bindHandler.bind(self)); + }, + + /** + * @param {Object} element + * @param {Number} delay + */ + bindHandler: function (element, delay) { + var self = this; + + delay = typeof delay === 'undefined' ? self.validateDelay : delay; + + element.on('value', function () { + clearTimeout(self.validateZipCodeTimeout); + self.validateZipCodeTimeout = setTimeout(function () { + self.postcodeValidation(element); + }, delay); + }); + }, + + /** + * @param {Object} postcodeElement + * @return {*} + */ + postcodeValidation: function (postcodeElement) { + var countryId = $('select[name="country_id"]:visible').val(), + validationResult, + warnMessage; + + if (postcodeElement == null || postcodeElement.value() == null) { + return true; + } + + postcodeElement.warn(null); + validationResult = postcodeValidator.validate(postcodeElement.value(), countryId); + + if (!validationResult) { + warnMessage = $t('Provided Zip/Postal Code seems to be invalid.'); + + if (postcodeValidator.validatedPostCodeExample.length) { + warnMessage += $t(' Example: ') + postcodeValidator.validatedPostCodeExample.join('; ') + '. '; + } + warnMessage += $t('If you believe it is the right one you can ignore this notice.'); + postcodeElement.warn(warnMessage); + } + + return validationResult; + } + }; +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js index 0e94232786c65..d8fea6ea329e8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js @@ -87,17 +87,7 @@ define([ data.shippingCarrierCode = quote.shippingMethod()['carrier_code']; } - if (!cartCache.isChanged('cartVersion', customerData.get('cart')()['data_id']) && - !cartCache.isChanged('shippingMethodCode', data.shippingMethodCode) && - !cartCache.isChanged('shippingCarrierCode', data.shippingCarrierCode) && - !cartCache.isChanged('address', address) && - cartCache.get('totals') && - !cartCache.isChanged('subtotal', parseFloat(quote.totals().subtotal)) - ) { - quote.setTotals(cartCache.get('totals')); - } else { - return loadFromServer(address); - } + return loadFromServer(address); } }; }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js index 42b692ff9dd8d..bf1697650e762 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js @@ -29,7 +29,9 @@ define([ try { error = JSON.parse(response.responseText); } catch (exception) { - error = $t('Something went wrong with your request. Please try again later.'); + error = { + message: $t('Something went wrong with your request. Please try again later.') + }; } messageContainer.addErrorMessage(error); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js index cf2a59cdba427..a9cbb1194cfd3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js @@ -7,7 +7,7 @@ define([ 'jquery', 'mage/template', 'underscore', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/validation' ], function ($, mageTemplate, _) { 'use strict'; @@ -157,7 +157,7 @@ define([ regionInput = $(this.options.regionInputId), postcode = $(this.options.postcodeId), label = regionList.parent().siblings('label'), - requiredLabel = regionList.parents('div.field'); + container = regionList.parents('div.field'); this._clearError(); this._checkRegionRequired(country); @@ -181,15 +181,16 @@ define([ if (this.options.isRegionRequired) { regionList.addClass('required-entry').removeAttr('disabled'); - requiredLabel.addClass('required'); + container.addClass('required').show(); } else { regionList.removeClass('required-entry validate-select').removeAttr('data-validate'); - requiredLabel.removeClass('required'); + container.removeClass('required'); if (!this.options.optionalRegionAllowed) { //eslint-disable-line max-depth - regionList.attr('disabled', 'disabled'); + regionList.hide(); + container.hide(); } else { - regionList.removeAttr('disabled'); + regionList.show(); } } @@ -201,12 +202,13 @@ define([ if (this.options.isRegionRequired) { regionInput.addClass('required-entry').removeAttr('disabled'); - requiredLabel.addClass('required'); + container.addClass('required').show(); } else { if (!this.options.optionalRegionAllowed) { //eslint-disable-line max-depth regionInput.attr('disabled', 'disabled'); + container.hide(); } - requiredLabel.removeClass('required'); + container.removeClass('required'); regionInput.removeClass('required-entry'); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js index eecfa65b189d1..39bd07f0c73a0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js @@ -5,7 +5,7 @@ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index e66c66006246c..472f7588d5b6c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -10,10 +10,11 @@ define([ 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/modal/confirm', 'underscore', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/decorate', 'mage/collapsible', - 'mage/cookies' + 'mage/cookies', + 'jquery-ui-modules/effect-fade' ], function ($, authenticationPopup, customerData, alert, confirm, _) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index a552aa01da061..59d1daa757138 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -18,7 +18,7 @@ define([ 'Magento_Checkout/js/action/set-billing-address', 'Magento_Ui/js/model/messageList', 'mage/translate', - 'Magento_Checkout/js/model/shipping-rates-validator' + 'Magento_Checkout/js/model/billing-address-postcode-validator' ], function ( ko, @@ -35,7 +35,7 @@ function ( setBillingAddressAction, globalMessageList, $t, - shippingRatesValidator + billingAddressPostcodeValidator ) { 'use strict'; @@ -66,7 +66,7 @@ function ( quote.paymentMethod.subscribe(function () { checkoutDataResolver.resolveBillingAddress(); }, this); - shippingRatesValidator.initFields(this.get('name') + '.form-fields'); + billingAddressPostcodeValidator.initFields(this.get('name') + '.form-fields'); }, /** diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js index 5e29fa209a641..d152f94397730 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js @@ -102,8 +102,9 @@ define([ self.isLoading(true); }); - if (cartData().website_id !== window.checkout.websiteId || - cartData().store_id !== window.checkout.storeId + if ( + cartData().website_id !== window.checkout.websiteId && + cartData().website_id !== undefined ) { customerData.reload(['cart'], false); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js index 7b200860c4d55..1b5463c0770a3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js @@ -133,15 +133,14 @@ define([ event.preventDefault(); } - if (this.validate() && additionalValidators.validate()) { + if (this.validate() && + additionalValidators.validate() && + this.isPlaceOrderActionAllowed() === true + ) { this.isPlaceOrderActionAllowed(false); this.getPlaceOrderDeferredObject() - .fail( - function () { - self.isPlaceOrderActionAllowed(true); - } - ).done( + .done( function () { self.afterPlaceOrder(); @@ -149,6 +148,10 @@ define([ redirectOnSuccessAction.execute(); } } + ).always( + function () { + self.isPlaceOrderActionAllowed(true); + } ); return true; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/summary/cart-items.js b/app/code/Magento/Checkout/view/frontend/web/js/view/summary/cart-items.js index 488bcfd67594c..003fef9f15d3f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/summary/cart-items.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/summary/cart-items.js @@ -12,6 +12,8 @@ define([ ], function (ko, totals, Component, stepNavigator, quote) { 'use strict'; + var useQty = window.checkoutConfig.useQty; + return Component.extend({ defaults: { template: 'Magento_Checkout/summary/cart-items' @@ -44,6 +46,15 @@ define([ return parseInt(totals.getItems()().length, 10); }, + /** + * Returns shopping cart items summary (includes config settings) + * + * @returns {Number} + */ + getCartSummaryItemsCount: function () { + return useQty ? this.getItemsQty() : this.getCartLineItemsCount(); + }, + /** * @inheritdoc */ diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index ea521b3a8afd4..a0827d17d6622 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -13,21 +13,19 @@ <a if="currentBillingAddress().telephone" attr="'href': 'tel:' + currentBillingAddress().telephone" text="currentBillingAddress().telephone"></a><br/> <each args="data: currentBillingAddress().customAttributes, as: 'element'"> - <each args="data: Object.keys(element), as: 'attribute'"> - <if args="typeof element[attribute] === 'object'"> - <if args="element[attribute].label"> - <text args="element[attribute].label"/> - </if> - <ifnot args="element[attribute].label"> - <if args="element[attribute].value"> - <text args="element[attribute].value"/> - </if> - </ifnot> + <if args="typeof element === 'object'"> + <if args="element.label"> + <text args="element.label"/> </if> - <if args="typeof element[attribute] === 'string'"> - <text args="element[attribute]"/> - </if><br/> - </each> + <ifnot args="element.label"> + <if args="element.value"> + <text args="element.value"/> + </if> + </ifnot> + </if> + <if args="typeof element === 'string'"> + <text args="element"/> + </if><br/> </each> <button visible="!isAddressSameAsShipping()" diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html index fb128a891aea2..0719a7d01ec70 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html @@ -29,7 +29,7 @@ <div class="items-total"> <span class="count" if="maxItemsToDisplay < getCartLineItemsCount()" text="maxItemsToDisplay"/> <translate args="'of'" if="maxItemsToDisplay < getCartLineItemsCount()"/> - <span class="count" text="getCartLineItemsCount()"/> + <span class="count" text="getCartParam('summary_count')"/> <!-- ko if: (getCartLineItemsCount() === 1) --> <span translate="'Item in Cart'"/> <!--/ko--> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/registration.html b/app/code/Magento/Checkout/view/frontend/web/template/registration.html index ea94726e5443e..5cc0d189e7c5b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/registration.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/registration.html @@ -11,8 +11,8 @@ <!-- ko if: isFormVisible --> <p data-bind="i18n: 'You can track your order status by creating an account.'"></p> <p><span data-bind="i18n: 'Email Address'"></span>: <span data-bind="text: getEmailAddress()"></span></p> - <form method="get" data-bind="attr: { action: getUrl() }"> - <input type="submit" class="action primary" data-bind="value: $t('Create an Account')" /> - </form> + <a class="action primary" data-bind="attr: { href: getUrl() }"> + <span data-bind="i18n: 'Create an Account'" /> + </a> <!--/ko--> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index 541413955cb47..75e061426d816 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -13,20 +13,18 @@ <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> <each args="data: address().customAttributes, as: 'element'"> - <each args="data: Object.keys(element), as: 'attribute'"> - <if args="typeof element[attribute] === 'object'"> - <if args="element[attribute].label"> - <text args="element[attribute].label"/> - </if> - <ifnot args="element[attribute].label"> - <if args="element[attribute].value"> - <text args="element[attribute].value"/> - </if> - </ifnot> + <if args="typeof element === 'object'"> + <if args="element.label"> + <text args="element.label"/> </if> - <if args="typeof element[attribute] === 'string'"> - <text args="element[attribute]"/> - </if><br/> - </each> + <ifnot args="element.label"> + <if args="element.value"> + <text args="element.value"/> + </if> + </ifnot> + </if> + <if args="typeof element === 'string'"> + <text args="element"/> + </if><br/> </each> </if> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html index fc74a4691a2e7..4e49d4502d8a8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html @@ -9,7 +9,7 @@ <strong role="heading" aria-level="1"> <translate args="maxCartItemsToDisplay" if="maxCartItemsToDisplay < getCartLineItemsCount()"/> <translate args="'of'" if="maxCartItemsToDisplay < getCartLineItemsCount()"/> - <span data-bind="text: getCartLineItemsCount()"></span> + <span data-bind="text: getCartSummaryItemsCount()"></span> <translate args="'Item in Cart'" if="getCartLineItemsCount() === 1"/> <translate args="'Items in Cart'" if="getCartLineItemsCount() > 1"/> </strong> diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index 4744d069ca9ea..dfe2bf3aaed04 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-checkout": "100.3.*", @@ -25,5 +25,5 @@ "Magento\\CheckoutAgreements\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/additional_agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/additional_agreements.phtml index 28a6e998d8d4e..9013a39f8e6f6 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/additional_agreements.phtml +++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/additional_agreements.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @var $block \Magento\CheckoutAgreements\Block\Agreements */ diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/agreements.phtml index b0c6384bcc9fe..5cb256090c196 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/agreements.phtml +++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/agreements.phtml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Files.LineLength ?> <?php @@ -17,30 +17,42 @@ } ?> <ol id="checkout-agreements" class="agreements checkout items"> <?php /** @var \Magento\CheckoutAgreements\Api\Data\AgreementInterface $agreement */ ?> - <?php foreach ($block->getAgreements() as $agreement): ?> + <?php foreach ($block->getAgreements() as $agreement) :?> <li class="item"> - <div class="checkout-agreement-item-content"<?= ($agreement->getContentHeight() ? ' style="height:' . $agreement->getContentHeight() . '"' : '') ?>> - <?php if ($agreement->getIsHtml()):?> - <?= /* @escapeNotVerified */ $agreement->getContent() ?> - <?php else:?> - <?= nl2br($block->escapeHtml($agreement->getContent())) ?> + <div class="checkout-agreement-item-content"<?= $block->escapeHtmlAttr($agreement->getContentHeight() ? ' style="height:' . $agreement->getContentHeight() . '"' : '') ?>> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getContent() ?> + <?php else :?> + <?= $block->escapeHtml(nl2br($agreement->getContent())) ?> <?php endif; ?> </div> - <form id="checkout-agreements-form-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" class="field choice agree required"> - <?php if($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_MANUAL): ?> + <form id="checkout-agreements-form-<?= (int) $agreement->getAgreementId() ?>" class="field choice agree required"> + <?php if ($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_MANUAL) :?> <input type="checkbox" - id="agreement-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" - name="agreement[<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>]" + id="agreement-<?= (int) $agreement->getAgreementId() ?>" + name="agreement[<?= (int) $agreement->getAgreementId() ?>]" value="1" title="<?= $block->escapeHtml($agreement->getCheckboxText()) ?>" class="checkbox" data-validate="{required:true}"/> - <label class="label" for="agreement-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>"> - <span><?= $agreement->getIsHtml() ? $agreement->getCheckboxText() : $block->escapeHtml($agreement->getCheckboxText()) ?></span> + <label class="label" for="agreement-<?= (int) $agreement->getAgreementId() ?>"> + <span> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getCheckboxText() ?> + <?php else :?> + <?= $block->escapeHtml($agreement->getCheckboxText()) ?> + <?php endif; ?> + </span> </label> - <?php elseif($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO): ?> - <div id="checkout-agreements-form-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" class="field choice agree"> - <span><?= $agreement->getIsHtml() ? $agreement->getCheckboxText() : $block->escapeHtml($agreement->getCheckboxText()) ?></span> + <?php elseif ($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO) :?> + <div id="checkout-agreements-form-<?= (int) $agreement->getAgreementId() ?>" class="field choice agree"> + <span> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getCheckboxText() ?> + <?php else :?> + <?= $block->escapeHtml($agreement->getCheckboxText()) ?> + <?php endif; ?> + </span> </div> <?php endif; ?> </form> diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml index 33227f0cdce3c..fb2d5168d21de 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml +++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml @@ -5,7 +5,7 @@ */ // @deprecated -// @codingStandardsIgnoreFile +// phpcs:disable Magento2.Files.LineLength ?> <?php @@ -18,31 +18,43 @@ } ?> <ol id="checkout-agreements" class="agreements checkout items"> <?php /** @var \Magento\CheckoutAgreements\Api\Data\AgreementInterface $agreement */ ?> - <?php foreach ($block->getAgreements() as $agreement): ?> + <?php foreach ($block->getAgreements() as $agreement) :?> <li class="item"> - <div class="checkout-agreement-item-content"<?= ($agreement->getContentHeight() ? ' style="height:' . $agreement->getContentHeight() . '"' : '') ?>> - <?php if ($agreement->getIsHtml()):?> - <?= /* @escapeNotVerified */ $agreement->getContent() ?> - <?php else:?> - <?= nl2br($block->escapeHtml($agreement->getContent())) ?> + <div class="checkout-agreement-item-content"<?= $block->escapeHtmlAttr($agreement->getContentHeight() ? ' style="height:' . $agreement->getContentHeight() . '"' : '') ?>> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getContent() ?> + <?php else :?> + <?= $block->escapeHtml(nl2br($agreement->getContent())) ?> <?php endif; ?> </div> - <?php if($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_MANUAL): ?> - <div id="checkout-agreements-form-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" class="field choice agree required"> + <?php if ($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_MANUAL) :?> + <div id="checkout-agreements-form-<?= (int) $agreement->getAgreementId() ?>" class="field choice agree required"> <input type="checkbox" - id="agreement-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" - name="agreement[<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>]" + id="agreement-<?= (int) $agreement->getAgreementId() ?>" + name="agreement[<?= (int) $agreement->getAgreementId() ?>]" value="1" title="<?= $block->escapeHtml($agreement->getCheckboxText()) ?>" class="checkbox" data-validate="{required:true}"/> - <label class="label" for="agreement-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>"> - <span><?= $agreement->getIsHtml() ? $agreement->getCheckboxText() : $block->escapeHtml($agreement->getCheckboxText()) ?></span> + <label class="label" for="agreement-<?= (int) $agreement->getAgreementId() ?>"> + <span> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getCheckboxText() ?> + <?php else :?> + <?= $block->escapeHtml($agreement->getCheckboxText()) ?> + <?php endif; ?> + </span> </label> </div> - <?php elseif($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO): ?> - <div id="checkout-agreements-form-<?= /* @escapeNotVerified */ $agreement->getAgreementId() ?>" class="field choice agree"> - <span><?= $agreement->getIsHtml() ? $agreement->getCheckboxText() : $block->escapeHtml($agreement->getCheckboxText()) ?></span> + <?php elseif ($agreement->getMode() == \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO) :?> + <div id="checkout-agreements-form-<?= (int) $agreement->getAgreementId() ?>" class="field choice agree"> + <span> + <?php if ($agreement->getIsHtml()) :?> + <?= /* @noEscape */ $agreement->getCheckboxText() ?> + <?php else :?> + <?= $block->escapeHtml($agreement->getCheckboxText()) ?> + <?php endif; ?> + </span> </div> <?php endif; ?> </li> diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php b/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php new file mode 100644 index 0000000000000..3daf88226d8e5 --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CheckoutAgreementsGraphQl\Model\Resolver; + +use Magento\CheckoutAgreements\Model\AgreementModeOptions; +use Magento\CheckoutAgreements\Model\ResourceModel\Agreement\Collection; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CheckoutAgreements\Api\Data\AgreementInterface; +use Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Checkout Agreements resolver, used for GraphQL request processing + */ +class CheckoutAgreements implements ResolverInterface +{ + /** + * @var CollectionFactory + */ + private $agreementCollectionFactory; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param CollectionFactory $agreementCollectionFactory + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + CollectionFactory $agreementCollectionFactory, + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig + ) { + $this->agreementCollectionFactory = $agreementCollectionFactory; + $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->scopeConfig->isSetFlag('checkout/options/enable_agreements', ScopeInterface::SCOPE_STORE)) { + return []; + } + + /** @var Collection $agreementsCollection */ + $agreementsCollection = $this->agreementCollectionFactory->create(); + $agreementsCollection->addStoreFilter($this->storeManager->getStore()->getId()); + $agreementsCollection->addFieldToFilter(AgreementInterface::IS_ACTIVE, 1); + + $checkoutAgreementData = []; + /** @var AgreementInterface $checkoutAgreement */ + foreach ($agreementsCollection->getItems() as $checkoutAgreement) { + $checkoutAgreementData[] = [ + AgreementInterface::AGREEMENT_ID => $checkoutAgreement->getAgreementId(), + AgreementInterface::CONTENT => $checkoutAgreement->getContent(), + AgreementInterface::NAME => $checkoutAgreement->getName(), + AgreementInterface::CONTENT_HEIGHT => $checkoutAgreement->getContentHeight(), + AgreementInterface::CHECKBOX_TEXT => $checkoutAgreement->getCheckboxText(), + AgreementInterface::IS_HTML => $checkoutAgreement->getIsHtml(), + AgreementInterface::MODE => + AgreementModeOptions::MODE_AUTO === (int)$checkoutAgreement->getMode() ? 'AUTO' : 'MANUAL', + ]; + } + return $checkoutAgreementData; + } +} diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/README.md b/app/code/Magento/CheckoutAgreementsGraphQl/README.md new file mode 100644 index 0000000000000..3ef735e3937f5 --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/README.md @@ -0,0 +1,4 @@ +# CheckoutAgreementsGraphQl + +**CheckoutAgreementsGraphQl** provides type information for the GraphQl module +to generate Checkout Agreements fields for Checkout Agreements information endpoints. diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json new file mode 100644 index 0000000000000..98e8f377d2a37 --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-checkout-agreements-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "102.0.*", + "magento/module-store": "101.0.*", + "magento/module-checkout-agreements": "100.3.*" + }, + "suggest": { + "magento/module-graph-ql": "100.3.*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CheckoutAgreementsGraphQl\\": "" + } + }, + "version": "100.3.0" +} diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/etc/module.xml b/app/code/Magento/CheckoutAgreementsGraphQl/etc/module.xml new file mode 100644 index 0000000000000..d18e8ee17d097 --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_CheckoutAgreementsGraphQl" /> +</config> diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/etc/schema.graphqls b/app/code/Magento/CheckoutAgreementsGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..64ef9411dfca6 --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/etc/schema.graphqls @@ -0,0 +1,21 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Query { + checkoutAgreements: [CheckoutAgreement] @resolver(class: "Magento\\CheckoutAgreementsGraphQl\\Model\\Resolver\\CheckoutAgreements") @doc(description: "The Checkout Agreements information") +} + +type CheckoutAgreement @doc(description: "Defines all Checkout Agreement information") { + agreement_id: Int! @doc(description: "Checkout Agreement identifier") + name: String! @doc(description: "Checkout Agreement name") + content: String! @doc(description: "Checkout Agreement content") + content_height: String @doc(description: "Checkout Agreement content height") + checkbox_text: String! @doc(description: "Checkout Agreement checkbox text") + is_html: Boolean! @doc(description: "Is Checkout Agreement content in HTML format") + mode: CheckoutAgreementMode! +} + +enum CheckoutAgreementMode { + AUTO + MANUAL +} diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/registration.php b/app/code/Magento/CheckoutAgreementsGraphQl/registration.php new file mode 100644 index 0000000000000..b0b4839f33d1f --- /dev/null +++ b/app/code/Magento/CheckoutAgreementsGraphQl/registration.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_CheckoutAgreementsGraphQl', __DIR__); diff --git a/app/code/Magento/Cms/Block/Block.php b/app/code/Magento/Cms/Block/Block.php index c611f4b1e9f05..86cf059525e1e 100644 --- a/app/code/Magento/Cms/Block/Block.php +++ b/app/code/Magento/Cms/Block/Block.php @@ -13,6 +13,11 @@ */ class Block extends AbstractBlock implements \Magento\Framework\DataObject\IdentityInterface { + /** + * Prefix for cache key of CMS block + */ + const CACHE_KEY_PREFIX = 'CMS_BLOCK_'; + /** * @var \Magento\Cms\Model\Template\FilterProvider */ diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php index 04557ddaeec78..d0ee1453eda10 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php @@ -7,6 +7,8 @@ use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\View\Result\PageFactory; /** @@ -26,16 +28,24 @@ class Index extends \Magento\Backend\App\Action implements HttpGetActionInterfac */ protected $resultPageFactory; + /** + * @var DataPersistorInterface + */ + private $dataPersistor; + /** * @param Context $context * @param PageFactory $resultPageFactory + * @param DataPersistorInterface $dataPersistor */ public function __construct( Context $context, - PageFactory $resultPageFactory + PageFactory $resultPageFactory, + DataPersistorInterface $dataPersistor = null ) { parent::__construct($context); $this->resultPageFactory = $resultPageFactory; + $this->dataPersistor = $dataPersistor ?: ObjectManager::getInstance()->get(DataPersistorInterface::class); } /** @@ -52,8 +62,7 @@ public function execute() $resultPage->addBreadcrumb(__('Manage Pages'), __('Manage Pages')); $resultPage->getConfig()->getTitle()->prepend(__('Pages')); - $dataPersistor = $this->_objectManager->get(\Magento\Framework\App\Request\DataPersistorInterface::class); - $dataPersistor->clear('cms_page'); + $this->dataPersistor->clear('cms_page'); return $resultPage; } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php index 8774d7e69adfe..2237f35ed0b84 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php @@ -7,6 +7,7 @@ use Magento\Backend\App\Action\Context; use Magento\Cms\Api\PageRepositoryInterface as PageRepository; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Cms\Api\Data\PageInterface; @@ -15,7 +16,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class InlineEdit extends \Magento\Backend\App\Action +class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -56,6 +57,8 @@ public function __construct( } /** + * Process the request + * * @return \Magento\Framework\Controller\ResultInterface * @throws \Magento\Framework\Exception\LocalizedException */ @@ -68,10 +71,12 @@ public function execute() $postItems = $this->getRequest()->getParam('items', []); if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { - return $resultJson->setData([ - 'messages' => [__('Please correct the data sent.')], - 'error' => true, - ]); + return $resultJson->setData( + [ + 'messages' => [__('Please correct the data sent.')], + 'error' => true, + ] + ); } foreach (array_keys($postItems) as $pageId) { @@ -98,10 +103,12 @@ public function execute() } } - return $resultJson->setData([ - 'messages' => $messages, - 'error' => $error - ]); + return $resultJson->setData( + [ + 'messages' => $messages, + 'error' => $error + ] + ); } /** @@ -131,7 +138,7 @@ protected function filterPost($postData = []) */ protected function validatePost(array $pageData, \Magento\Cms\Model\Page $page, &$error, array &$messages) { - if (!($this->dataProcessor->validate($pageData) && $this->dataProcessor->validateRequireEntry($pageData))) { + if (!$this->dataProcessor->validateRequireEntry($pageData)) { $error = true; foreach ($this->messageManager->getMessages(true)->getItems() as $error) { $messages[] = $this->getErrorWithPageId($page, $error->getText()); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php index 9b8933c8dba2e..e865eee8d6f21 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php @@ -12,8 +12,7 @@ use Magento\Framework\Config\Dom\ValidationSchemaException; /** - * Class PostDataProcessor - * @package Magento\Cms\Controller\Adminhtml\Page + * Processes form data */ class PostDataProcessor { @@ -80,6 +79,7 @@ public function filter($data) * * @param array $data * @return bool Return FALSE if some item is invalid + * @deprecated 103.0.3 */ public function validate($data) { diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php index 37cb45753174f..569f6b256163f 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php @@ -107,10 +107,6 @@ public function execute() ['page' => $model, 'request' => $this->getRequest()] ); - if (!$this->dataProcessor->validate($data)) { - return $resultRedirect->setPath('*/*/edit', ['page_id' => $model->getId(), '_current' => true]); - } - try { $this->pageRepository->save($model); $this->messageManager->addSuccessMessage(__('You saved the page.')); diff --git a/app/code/Magento/Cms/Model/Block.php b/app/code/Magento/Cms/Model/Block.php index e65675ceee9ec..9da444c72e80c 100644 --- a/app/code/Magento/Cms/Model/Block.php +++ b/app/code/Magento/Cms/Model/Block.php @@ -12,8 +12,8 @@ /** * CMS block model * - * @method Block setStoreId(array $storeId) - * @method array getStoreId() + * @method Block setStoreId(int $storeId) + * @method int getStoreId() */ class Block extends AbstractModel implements BlockInterface, IdentityInterface { @@ -41,6 +41,8 @@ class Block extends AbstractModel implements BlockInterface, IdentityInterface protected $_eventPrefix = 'cms_block'; /** + * Construct. + * * @return void */ protected function _construct() @@ -61,7 +63,7 @@ public function beforeSave() } $needle = 'block_id="' . $this->getId() . '"'; - if (false == strstr($this->getContent(), $needle)) { + if (false == strstr($this->getContent(), (string) $needle)) { return parent::beforeSave(); } throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Cms/Model/Page.php b/app/code/Magento/Cms/Model/Page.php index d950f484cd1d9..8eefe26236ba5 100644 --- a/app/code/Magento/Cms/Model/Page.php +++ b/app/code/Magento/Cms/Model/Page.php @@ -16,8 +16,8 @@ * Cms Page Model * * @api - * @method Page setStoreId(array $storeId) - * @method array getStoreId() + * @method Page setStoreId(int $storeId) + * @method int getStoreId() * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @since 100.0.2 */ @@ -103,8 +103,7 @@ public function getStores() } /** - * Check if page identifier exist for specific store - * return page id if page exists + * Check if page identifier exist for specific store return page id if page exists * * @param string $identifier * @param int $storeId @@ -116,8 +115,7 @@ public function checkIdentifier($identifier, $storeId) } /** - * Prepare page's statuses. - * Available event cms_page_get_available_statuses to customize statuses. + * Prepare page's statuses, available event cms_page_get_available_statuses to customize statuses. * * @return array */ @@ -538,7 +536,7 @@ public function setIsActive($isActive) } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function beforeSave() @@ -571,6 +569,8 @@ public function beforeSave() } /** + * Returns scope config. + * * @return ScopeConfigInterface */ private function getScopeConfig() diff --git a/app/code/Magento/Cms/Model/PageRepository.php b/app/code/Magento/Cms/Model/PageRepository.php index 305700782dae4..898ae08a1e7ef 100644 --- a/app/code/Magento/Cms/Model/PageRepository.php +++ b/app/code/Magento/Cms/Model/PageRepository.php @@ -246,10 +246,9 @@ public function delete(\Magento\Cms\Api\Data\PageInterface $page) try { $this->resource->delete($page); } catch (\Exception $exception) { - throw new CouldNotDeleteException(__( - 'Could not delete the page: %1', - $exception->getMessage() - )); + throw new CouldNotDeleteException( + __('Could not delete the page: %1', $exception->getMessage()) + ); } return true; } diff --git a/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php b/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php new file mode 100644 index 0000000000000..9fd94d4c11e1c --- /dev/null +++ b/app/code/Magento/Cms/Model/PageRepository/ValidationComposite.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\PageRepository; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaInterface; + +/** + * Validates and saves a page + */ +class ValidationComposite implements PageRepositoryInterface +{ + /** + * @var PageRepositoryInterface + */ + private $repository; + + /** + * @var array + */ + private $validators; + + /** + * @param PageRepositoryInterface $repository + * @param ValidatorInterface[] $validators + */ + public function __construct( + PageRepositoryInterface $repository, + array $validators = [] + ) { + foreach ($validators as $validator) { + if (!$validator instanceof ValidatorInterface) { + throw new \InvalidArgumentException( + sprintf('Supplied validator does not implement %s', ValidatorInterface::class) + ); + } + } + $this->repository = $repository; + $this->validators = $validators; + } + + /** + * @inheritdoc + */ + public function save(PageInterface $page) + { + foreach ($this->validators as $validator) { + $validator->validate($page); + } + + return $this->repository->save($page); + } + + /** + * @inheritdoc + */ + public function getById($pageId) + { + return $this->repository->getById($pageId); + } + + /** + * @inheritdoc + */ + public function getList(SearchCriteriaInterface $searchCriteria) + { + return $this->repository->getList($searchCriteria); + } + + /** + * @inheritdoc + */ + public function delete(PageInterface $page) + { + return $this->repository->delete($page); + } + + /** + * @inheritdoc + */ + public function deleteById($pageId) + { + return $this->repository->deleteById($pageId); + } +} diff --git a/app/code/Magento/Cms/Model/PageRepository/Validator/LayoutUpdateValidator.php b/app/code/Magento/Cms/Model/PageRepository/Validator/LayoutUpdateValidator.php new file mode 100644 index 0000000000000..721fd9efbdd91 --- /dev/null +++ b/app/code/Magento/Cms/Model/PageRepository/Validator/LayoutUpdateValidator.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\PageRepository\Validator; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Model\PageRepository\ValidatorInterface; +use Magento\Framework\Config\Dom\ValidationException; +use Magento\Framework\Config\Dom\ValidationSchemaException; +use Magento\Framework\Config\ValidationStateInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\View\Model\Layout\Update\Validator; +use Magento\Framework\View\Model\Layout\Update\ValidatorFactory; + +/** + * Validate a given page + */ +class LayoutUpdateValidator implements ValidatorInterface +{ + /** + * @var ValidatorFactory + */ + private $validatorFactory; + + /** + * @var ValidationStateInterface + */ + private $validationState; + + /** + * @param ValidatorFactory $validatorFactory + * @param ValidationStateInterface $validationState + */ + public function __construct( + ValidatorFactory $validatorFactory, + ValidationStateInterface $validationState + ) { + $this->validatorFactory = $validatorFactory; + $this->validationState = $validationState; + } + + /** + * Validate the data before saving + * + * @param PageInterface $page + * @throws LocalizedException + */ + public function validate(PageInterface $page): void + { + $this->validateRequiredFields($page); + $this->validateLayoutUpdate($page); + $this->validateCustomLayoutUpdate($page); + } + + /** + * Validate required fields + * + * @param PageInterface $page + * @throws LocalizedException + */ + private function validateRequiredFields(PageInterface $page): void + { + if (empty($page->getTitle())) { + throw new LocalizedException(__('Required field "%1" is empty.', 'title')); + } + } + + /** + * Validate layout update + * + * @param PageInterface $page + * @throws LocalizedException + */ + private function validateLayoutUpdate(PageInterface $page): void + { + $layoutXmlValidator = $this->getLayoutValidator(); + + try { + if (!empty($page->getLayoutUpdateXml()) + && !$layoutXmlValidator->isValid($page->getLayoutUpdateXml()) + ) { + throw new LocalizedException(__('Layout update is invalid')); + } + } catch (ValidationException|ValidationSchemaException $e) { + throw new LocalizedException(__('Layout update is invalid')); + } + } + + /** + * Validate custom layout update + * + * @param PageInterface $page + * @throws LocalizedException + */ + private function validateCustomLayoutUpdate(PageInterface $page): void + { + $layoutXmlValidator = $this->getLayoutValidator(); + + try { + if (!empty($page->getCustomLayoutUpdateXml()) + && !$layoutXmlValidator->isValid($page->getCustomLayoutUpdateXml()) + ) { + throw new LocalizedException(__('Custom layout update is invalid')); + } + } catch (ValidationException|ValidationSchemaException $e) { + throw new LocalizedException(__('Custom layout update is invalid')); + } + } + + /** + * Return a new validator + * + * @return Validator + */ + private function getLayoutValidator(): Validator + { + return $this->validatorFactory->create( + [ + 'validationState' => $this->validationState, + ] + ); + } +} diff --git a/app/code/Magento/Cms/Model/PageRepository/ValidatorInterface.php b/app/code/Magento/Cms/Model/PageRepository/ValidatorInterface.php new file mode 100644 index 0000000000000..ff5c7648a9fa2 --- /dev/null +++ b/app/code/Magento/Cms/Model/PageRepository/ValidatorInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\PageRepository; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Framework\Exception\LocalizedException; + +/** + * Validate a page repository + */ +interface ValidatorInterface +{ + /** + * Assert the given page valid + * + * @param PageInterface $page + * @return void + * @throws LocalizedException + */ + public function validate(PageInterface $page): void; +} diff --git a/app/code/Magento/Cms/Model/Plugin/Product.php b/app/code/Magento/Cms/Model/Plugin/Product.php new file mode 100644 index 0000000000000..c8456d5cd6bfe --- /dev/null +++ b/app/code/Magento/Cms/Model/Plugin/Product.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Model\Plugin; + +use Magento\Catalog\Model\Product as CatalogProduct; +use Magento\Cms\Model\Page; + +/** + * Cleaning no-route page cache for the product details page after enabling product that is not assigned to a category + */ +class Product +{ + /** + * @var Page + */ + private $page; + + /** + * @param Page $page + */ + public function __construct(Page $page) + { + $this->page = $page; + } + + /** + * After get identities + * + * @param CatalogProduct $product + * @param array $identities + * @return array + */ + public function afterGetIdentities(CatalogProduct $product, array $identities) + { + if ($product->getOrigData('status') > $product->getData('status')) { + if (empty($product->getCategoryIds())) { + $noRoutePage = $this->page->load(Page::NOROUTE_PAGE_ID); + $noRoutePageId = $noRoutePage->getId(); + $identities[] = Page::CACHE_TAG . '_' . $noRoutePageId; + } + } + + return array_unique($identities); + } +} diff --git a/app/code/Magento/Cms/Model/Template/Filter.php b/app/code/Magento/Cms/Model/Template/Filter.php index 4f86dfe07dee2..7e71a06de1f31 100644 --- a/app/code/Magento/Cms/Model/Template/Filter.php +++ b/app/code/Magento/Cms/Model/Template/Filter.php @@ -39,6 +39,7 @@ public function setUseSessionInUrl($flag) */ public function mediaDirective($construction) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $params = $this->getParameters(html_entity_decode($construction[2], ENT_QUOTES)); if (preg_match('/\.\.(\\\|\/)/', $params['url'])) { throw new \InvalidArgumentException('Image path must be absolute'); diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index 6cfa43eb36e2c..953b4d455f52a 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -10,6 +10,7 @@ use Magento\Cms\Helper\Wysiwyg\Images; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; /** * Wysiwyg Images model. @@ -136,6 +137,21 @@ class Storage extends \Magento\Framework\DataObject */ protected $_uploaderFactory; + /** + * @var \Psr\Log\LoggerInterface|null + */ + private $logger; + + /** + * @var \Magento\Framework\Filesystem\DriverInterface + */ + private $file; + + /** + * @var \Magento\Framework\Filesystem\Io\File|null + */ + private $ioFile; + /** * Construct * @@ -155,7 +171,11 @@ class Storage extends \Magento\Framework\DataObject * @param array $extensions * @param array $dirs * @param array $data + * @param \Magento\Framework\Filesystem\DriverInterface $file + * @param \Magento\Framework\Filesystem\Io\File|null $ioFile + * @param \Psr\Log\LoggerInterface|null $logger * + * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -174,7 +194,10 @@ public function __construct( array $resizeParameters = [], array $extensions = [], array $dirs = [], - array $data = [] + array $data = [], + \Magento\Framework\Filesystem\DriverInterface $file = null, + \Magento\Framework\Filesystem\Io\File $ioFile = null, + \Psr\Log\LoggerInterface $logger = null ) { $this->_session = $session; $this->_backendUrl = $backendUrl; @@ -188,9 +211,12 @@ public function __construct( $this->_storageDatabaseFactory = $storageDatabaseFactory; $this->_directoryDatabaseFactory = $directoryDatabaseFactory; $this->_uploaderFactory = $uploaderFactory; + $this->logger = $logger ?: ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); $this->_resizeParameters = $resizeParameters; $this->_extensions = $extensions; $this->_dirs = $dirs; + $this->file = $file ?: ObjectManager::getInstance()->get(\Magento\Framework\Filesystem\Driver\File::class); + $this->ioFile = $ioFile ?: ObjectManager::getInstance()->get(\Magento\Framework\Filesystem\Io\File::class); parent::__construct($data); } @@ -288,9 +314,12 @@ public function getDirsCollection($path) /** * Return files * - * @param string $path Parent directory path - * @param string $type Type of storage, e.g. image, media etc. - * @return \Magento\Framework\Data\Collection\Filesystem + * @param string $path Parent directory path + * @param string $type Type of storage, e.g. image, media etc. + * @return \Magento\Framework\Data\Collection\Filesystem + * + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Magento\Framework\Exception\LocalizedException */ public function getFilesCollection($path, $type = null) { @@ -328,8 +357,8 @@ public function getFilesCollection($path, $type = null) $item->setName($item->getBasename()); $item->setShortName($this->_cmsWysiwygImages->getShortFilename($item->getBasename())); $item->setUrl($this->_cmsWysiwygImages->getCurrentUrl() . $item->getBasename()); - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $item->setSize(filesize($item->getFilename())); + $itemStats = $this->file->stat($item->getFilename()); + $item->setSize($itemStats['size']); $item->setMimeType(\mime_content_type($item->getFilename())); if ($this->isImage($item->getBasename())) { @@ -339,12 +368,15 @@ public function getFilesCollection($path, $type = null) $thumbUrl = $this->_backendUrl->getUrl('cms/*/thumbnail', ['file' => $item->getId()]); } - // phpcs:ignore Generic.PHP.NoSilencedErrors - $size = @getimagesize($item->getFilename()); + try { + $size = getimagesize($item->getFilename()); - if (is_array($size)) { - $item->setWidth($size[0]); - $item->setHeight($size[1]); + if (is_array($size)) { + $item->setWidth($size[0]); + $item->setHeight($size[1]); + } + } catch (\Error $e) { + $this->logger->notice(sprintf("GetImageSize caused error: %s", $e->getMessage())); } } else { $thumbUrl = $this->_assetRepo->getUrl(self::THUMB_PLACEHOLDER_PATH_SUFFIX); @@ -539,7 +571,7 @@ public function getThumbnailPath($filePath, $checkFile = false) { $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); - if (strpos($filePath, $mediaRootDir) === 0) { + if (strpos($filePath, (string) $mediaRootDir) === 0) { $thumbPath = $this->getThumbnailRoot() . substr($filePath, strlen($mediaRootDir)); if (!$checkFile || $this->_directory->isExist($this->_directory->getRelativePath($thumbPath))) { @@ -561,7 +593,7 @@ public function getThumbnailUrl($filePath, $checkFile = false) { $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); - if (strpos($filePath, $mediaRootDir) === 0) { + if (strpos($filePath, (string) $mediaRootDir) === 0) { $thumbSuffix = self::THUMBS_DIRECTORY_NAME . substr($filePath, strlen($mediaRootDir)); if (!$checkFile || $this->_directory->isExist( $this->_directory->getRelativePath($mediaRootDir . '/' . $thumbSuffix) @@ -605,8 +637,7 @@ public function resizeFile($source, $keepRatio = true) $image->open($source); $image->keepAspectRatio($keepRatio); $image->resize($this->_resizeParameters['width'], $this->_resizeParameters['height']); - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $dest = $targetDir . '/' . pathinfo($source, PATHINFO_BASENAME); + $dest = $targetDir . '/' . $this->ioFile->getPathInfo($source)['basename']; $image->save($dest); if ($this->_directory->isFile($this->_directory->getRelativePath($dest))) { return $dest; @@ -640,9 +671,8 @@ public function getThumbsPath($filePath = false) $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); $thumbnailDir = $this->getThumbnailRoot(); - if ($filePath && strpos($filePath, $mediaRootDir) === 0) { - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $thumbnailDir .= dirname(substr($filePath, strlen($mediaRootDir))); + if ($filePath && strpos($filePath, (string) $mediaRootDir) === 0) { + $thumbnailDir .= $this->file->getParentDirectory(substr($filePath, strlen($mediaRootDir))); } return $thumbnailDir; @@ -692,8 +722,11 @@ public function isImage($filename) if (!$this->hasData('_image_extensions')) { $this->setData('_image_extensions', $this->getAllowedExtensions('image')); } - // phpcs:ignore Magento2.Functions.DiscouragedFunction - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + $ext = ""; + if (array_key_exists('extension', $this->ioFile->getPathInfo($filename))) { + $ext = strtolower($this->ioFile->getPathInfo($filename)['extension']); + } return in_array($ext, $this->_getData('_image_extensions')); } @@ -742,7 +775,7 @@ protected function _validatePath($path) __('We can\'t delete root directory %1 right now.', $path) ); } - if (strpos($path, $root) !== 0) { + if (strpos($path, (string) $root) !== 0) { throw new \Magento\Framework\Exception\LocalizedException( __('Directory %1 is not under storage root path.', $path) ); diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminCMSPageMassActionSelectActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminCMSPageMassActionSelectActionGroup.xml new file mode 100644 index 0000000000000..2945538ed5f9f --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminCMSPageMassActionSelectActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCMSPageMassActionSelectActionGroup"> + <arguments> + <argument name="action" type="string" /> + </arguments> + <click selector="{{CmsPagesPageActionsSection.massActionsButton}}" stepKey="clickMassActionDropdown"/> + <click selector="{{CmsPagesPageActionsSection.massActionsOption(action)}}" stepKey="clickAction"/> + <waitForPageLoad stepKey="waitForPageToReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCMSPagesGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCMSPagesGridActionGroup.xml new file mode 100644 index 0000000000000..2439953cde0ec --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCMSPagesGridActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCMSPagesGridActionGroup"> + <amOnPage url="{{CmsPagesPage.url}}" stepKey="navigateToCMSPagesGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlockActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlockActionGroup.xml new file mode 100644 index 0000000000000..0f87ee90b7ce0 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCmsBlockActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCmsBlockActionGroup"> + <arguments> + <argument name="block_id" type="string"/> + </arguments> + <amOnPage url="{{AdminEditBlockPage.url(block_id)}}" stepKey="openEditCmsBlock"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpentCmsBlockActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpentCmsBlockActionGroup.xml new file mode 100644 index 0000000000000..0f87ee90b7ce0 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpentCmsBlockActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCmsBlockActionGroup"> + <arguments> + <argument name="block_id" type="string"/> + </arguments> + <amOnPage url="{{AdminEditBlockPage.url(block_id)}}" stepKey="openEditCmsBlock"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageInGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageInGridActionGroup.xml new file mode 100644 index 0000000000000..4de1157e3180b --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminSelectCMSPageInGridActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectCMSPageInGridActionGroup"> + <arguments> + <argument name="identifier" type="string"/> + </arguments> + <checkOption selector="{{CmsPagesPageActionsSection.pageRowCheckboxByIdentifier(identifier)}}" stepKey="selectCmsPageInGrid"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageInGridActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageInGridActionGroup.xml new file mode 100644 index 0000000000000..84feb0a16c4ef --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageInGridActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCMSPageInGridActionGroup"> + <arguments> + <argument name="cmsPage" type="entity" /> + </arguments> + + <seeElement stepKey="seeElementByCmsPageIdentifier" selector="{{AdminDataGridTableSection.rowTemplateStrict(cmsPage.identifier)}}" /> + <see userInput="{{cmsPage.title}}" stepKey="seeCmsPageTitle" selector="{{AdminDataGridTableSection.rowTemplateStrict(cmsPage.identifier)}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageNotFoundOnStorefrontActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageNotFoundOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..b92413a379454 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageNotFoundOnStorefrontActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCMSPageNotFoundOnStorefrontActionGroup"> + <see userInput="Whoops, our bad..." stepKey="seePageErrorNotFound"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml index 2fa1b86a61572..2c45b9e140c0f 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml @@ -21,4 +21,13 @@ <waitForPageLoad stepKey="waitSaveToBeApplied"/> <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the page." stepKey="seeSaveSuccess"/> </actionGroup> + <actionGroup name="ClearWidgetsForCMSHomePageContentWYSIWYGDisabled"> + <amOnPage url="{{CmsPageEditPage.url('2')}}" stepKey="navigateToEditHomePagePage"/> + <waitForPageLoad stepKey="waitForCmsPageEditPage"/> + <conditionalClick selector="{{CmsNewPagePageActionsSection.contentSectionName}}" dependentSelector="{{CatalogWidgetSection.insertWidgetButton}}" visible="false" stepKey="clickShowHideEditorIfVisible"/> + <waitForElementVisible selector="{{CmsNewPagePageContentSection.content}}" stepKey="waitForContentField"/> + <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="CMS homepage content goes here." stepKey="resetCMSPageToDefaultContent"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSettingsApply"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml index c51e673139af9..a459c41ccb41b 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml @@ -28,4 +28,10 @@ <click selector="{{CmsNewPageHierarchySection.header}}" stepKey="clickHierarchy"/> <click selector="{{CmsNewPageHierarchySection.selectHierarchy(selectHierarchyOpt)}}" stepKey="clickPageCheckBoxes"/> </actionGroup> + <actionGroup name="CreateNewPageWithAllValuesAndContent" extends="CreateNewPageWithAllValues"> + <arguments> + <argument name="pageContent" type="string"/> + </arguments> + <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="{{pageContent}}" stepKey="fillContentField" after="fillFieldContentHeading"/> + </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml new file mode 100644 index 0000000000000..0ec8db3f27c9c --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/StorefrontGoToCMSPageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontGoToCMSPageActionGroup"> + <arguments> + <argument name="identifier" type="string"/> + </arguments> + <amOnPage url="{{StorefrontHomePage.url}}{{identifier}}" stepKey="amOnCmsPageOnStorefront"/> + <waitForPageLoad stepKey="waitForPageLoadOnStorefront"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml index 368df3baa561f..98cd9ae30b8be 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml @@ -9,7 +9,7 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultBlock" type="block"> - <data key="title">Default Block</data> + <data key="title" unique="suffix">Default Block</data> <data key="identifier" unique="suffix" >block</data> <data key="content">Here is a block test. Yeah!</data> <data key="active">true</data> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml index 2f8efac37cecf..5dc100573c373 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml @@ -20,6 +20,15 @@ <data key="content">Sample page content. Yada yada yada.</data> <data key="identifier" unique="suffix">test-page-</data> </entity> + <entity name="customCmsPage" extends="_defaultCmsPage" type="cms_page"> + <data key="content">Test content data1</data> + <data key="identifier">url_key</data> + </entity> + <entity name="customCmsPage2" extends="_defaultCmsPage" type="cms_page"> + <data key="title">Test Second CMS Page</data> + <data key="content">Test content data2</data> + <data key="identifier">url_key</data> + </entity> <entity name="_duplicatedCMSPage" type="cms_page"> <data key="title">testpage</data> <data key="content_heading">Test Content Heading</data> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml b/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml new file mode 100644 index 0000000000000..46a968959407f --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="WysiwygEnabledByDefault"> + <data key="path">cms/wysiwyg/enabled</data> + <data key="scope_id">0</data> + <data key="value">enabled</data> + </entity> + <entity name="WysiwygDisabledByDefault"> + <data key="path">cms/wysiwyg/enabled</data> + <data key="scope_id">0</data> + <data key="value">hidden</data> + </entity> + <entity name="WysiwygTinyMCE3Enable"> + <data key="path">cms/wysiwyg/editor</data> + <data key="scope_id">0</data> + <data key="value">Magento_Tinymce3/tinymce3Adapter</data> + </entity> + <entity name="WysiwygTinyMCE4Enable"> + <data key="path">cms/wysiwyg/editor</data> + <data key="scope_id">0</data> + <data key="value">mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter</data> + </entity> +</entities> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/AdminCmsEditBlockPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/AdminCmsEditBlockPage.xml index 3fd100ee02aa2..f7ee5f5aca9b1 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/AdminCmsEditBlockPage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/AdminCmsEditBlockPage.xml @@ -8,7 +8,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> - <page name="AdminEditBlockPage" url="cms/block/edit/block_id" area="admin" module="Magento_Cms"> + <page name="AdminEditBlockPage" url="cms/block/edit/block_id/{{id}}" area="admin" module="Magento_Cms" parameterized="true"> <section name="AdminUpdateBlockSection"/> </page> </pages> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml index 11d8bb23313fb..494c98ca44e7f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml @@ -27,5 +27,8 @@ <element name="savePageSuccessMessage" type="text" selector=".message-success"/> <element name="delete" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Delete']" parameterized="true"/> <element name="deleteConfirm" type="button" selector=".action-primary.action-accept" timeout="60"/> + <element name="pageRowCheckboxByIdentifier" type="checkbox" selector="//table[@data-role='grid']//td[count(../../..//th[./*[.='URL Key']]/preceding-sibling::th) + 1][./*[.='{{identifier}}']]/../td//input[@data-action='select-row']" parameterized="true" /> + <element name="massActionsButton" type="button" selector="//div[@class='admin__data-grid-header'][(not(ancestor::*[@class='sticky-header']) and not(contains(@style,'visibility: hidden'))) or (ancestor::*[@class='sticky-header' and not(contains(@style,'display: none'))])]//button[contains(@class, 'action-select')]" /> + <element name="massActionsOption" type="button" selector="//div[@class='admin__data-grid-header'][(not(ancestor::*[@class='sticky-header']) and not(contains(@style,'visibility: hidden'))) or (ancestor::*[@class='sticky-header' and not(contains(@style,'display: none'))])]//span[contains(@class, 'action-menu-item') and .= '{{action}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml index ff6167ffc10e0..b1d0faa7507f0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml @@ -11,7 +11,7 @@ <section name="TinyMCESection"> <element name="checkIfContentTabOpen" type="button" selector="//span[text()='Content']/parent::strong/parent::*[@data-state-collapsible='closed']"/> <element name="CheckIfTabExpand" type="button" selector="//div[@data-state-collapsible='closed']//span[text()='Content']"/> - <element name="TinyMCE4" type="text" selector=".mce-branding-powered-by" /> + <element name="TinyMCE4" type="text" selector=".mce-branding" /> <element name="InsertWidgetBtn" type="button" selector=".action-add-widget"/> <element name="InsertWidgetIcon" type="button" selector="div[aria-label='Insert Widget']"/> <element name="InsertVariableBtn" type="button" selector=".scalable.add-variable.plugin"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index 03edc69e6d625..5baf75d43c53f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -16,6 +16,9 @@ <description value="Admin should be able to add image to WYSIWYG content of Block"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84376"/> + <skip> + <issueId value="MC-17232"/> + </skip> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index 205850f888797..e63a6be51bcc0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -16,6 +16,9 @@ <description value="Admin should be able to add image to WYSIWYG content of CMS Page"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-85825"/> + <skip> + <issueId value="MC-17232"/> + </skip> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml index 8fea72764f280..ce34a8d09c302 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml @@ -49,9 +49,9 @@ <waitForLoadingMaskToDisappear stepKey="waitForPageLoad3"/> <!--see Insert Variable button disabled--> <see selector="{{VariableSection.InsertVariableBtnDisabled}}" userInput="Insert Variable" stepKey="seeInsertWidgetDisabled" /> - <!--see Cancel button enabed--> + <!--see Cancel button enabled--> <see selector="{{VariableSection.CancelBtnEnabled}}" userInput="Cancel" stepKey="seeCancelBtnEnabled" /> - <!--see 4 colums--> + <!--see 4 columns--> <see selector="{{VariableSection.ColName('Select')}}" userInput="Select" stepKey="selectCol" /> <see selector="{{VariableSection.ColName('Variable Name')}}" userInput="Variable Name" stepKey="variableCol" /> <see selector="{{VariableSection.ColName('Type')}}" userInput="Type" stepKey="typeCol" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml index 9e5eb2558d6f2..3b501859e606e 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml @@ -43,9 +43,9 @@ <waitForText userInput="Insert Variable" stepKey="waitForSlideOutOpen"/> <!--see Insert Variable button disabled--> <see selector="{{VariableSection.InsertVariableBtnDisabled}}" userInput="Insert Variable" stepKey="seeInsertWidgetDisabled" /> - <!--see Cancel button enabed--> + <!--see Cancel button enabled--> <see selector="{{VariableSection.CancelBtnEnabled}}" userInput="Cancel" stepKey="seeCancelBtnEnabled" /> - <!--see 4 colums--> + <!--see 4 columns--> <see selector="{{VariableSection.ColName('Select')}}" userInput="Select" stepKey="selectCol" /> <see selector="{{VariableSection.ColName('Variable Name')}}" userInput="Variable Name" stepKey="variableCol" /> <see selector="{{VariableSection.ColName('Type')}}" userInput="Type" stepKey="typeCol" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 393e25e474f12..552eae407391d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -37,7 +37,7 @@ <see userInput="Inserting a widget does not create a widget instance." stepKey="seeMessage" /> <!--see Insert Widget button disabled--> <see selector="{{WidgetSection.InsertWidgetBtnDisabled}}" userInput="Insert Widget" stepKey="seeInsertWidgetDisabled" /> - <!--see Cancel button enabed--> + <!--see Cancel button enabled--> <see selector="{{WidgetSection.CancelBtnEnabled}}" userInput="Cancel" stepKey="seeCancelBtnEnabled" /> <!--Select "Widget Type"--> <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Catalog Category Link" stepKey="selectCatalogCategoryLink" /> @@ -64,9 +64,19 @@ <see userInput="Hello CMS Page!" stepKey="seeContent"/> <!--see widget on Storefront--> <see userInput="$$createPreReqCategory.name$$" stepKey="seeCategoryLink"/> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 0" stepKey="disableGenerateUrlRewrite"/> + <amOnPage url="{{_defaultCmsPage.identifier}}" stepKey="amOnPageTestPage2"/> + <waitForPageLoad stepKey="wait6" /> + <see userInput="Hello CMS Page!" stepKey="seeContent2"/> + <!--see widget on Storefront--> + <grabAttributeFrom selector=".widget a" userInput="href" stepKey="dataHref" /> + <assertRegExp expected="|$$createPreReqCategory.name$$.html|i" + expectedType="string" actual="$dataHref" actualType="variable" + stepKey="seeProductLinkInCategory"/> <after> <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCatalog" /> <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="enableGenerateUrlRewrite"/> <actionGroup ref="logout" stepKey="logout"/> </after> </test> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 9ee9d27de477a..d75d422afa2a4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -41,7 +41,7 @@ <waitForPageLoad stepKey="wait3"/> <!--see Insert Widget button disabled--> <see selector="{{WidgetSection.InsertWidgetBtnDisabled}}" userInput="Insert Widget" stepKey="seeInsertWidgetDisabled" /> - <!--see Cancel button enabed--> + <!--see Cancel button enabled--> <see selector="{{WidgetSection.CancelBtnEnabled}}" userInput="Cancel" stepKey="seeCancelBtnEnabled" /> <!--Select "Widget Type"--> <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Catalog Product Link" stepKey="selectCatalogProductLink" /> @@ -71,10 +71,19 @@ <!--see widget on Storefront--> <see userInput="Hello CMS Page!" stepKey="seeContent"/> <see userInput="$$createPreReqProduct.name$$" stepKey="seeProductLink"/> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 0" stepKey="disableGenerateUrlRewrite"/> + <amOnPage url="{{_defaultCmsPage.identifier}}" stepKey="amOnPageTestPage2"/> + <waitForPageLoad stepKey="wait8" /> + <!--see widget on Storefront--> + <grabAttributeFrom selector=".widget a" userInput="href" stepKey="dataHref" /> + <assertRegExp expected="|$$createPreReqCategory.name$$/$$createPreReqProduct.name$$.html|i" + expectedType="string" actual="$dataHref" actualType="variable" + stepKey="seeProductLinkInCategory"/> <after> <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCatalog" /> <deleteData createDataKey="createPreReqProduct" stepKey="deletePreReqProduct" /> <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="enableGenerateUrlRewrite"/> <actionGroup ref="logout" stepKey="logout"/> </after> </test> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageMassActionTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageMassActionTest.xml new file mode 100644 index 0000000000000..7cc0719dcbeb2 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageMassActionTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCmsPageMassActionTest"> + <annotations> + <features value="CmsPage"/> + <title value="Create two CMS Pages and perform mass disable action"/> + <description value="Admin should be able to perform mass actions to CMS pages"/> + <stories value="Admin Grid Mass Action" /> + <testCaseId value="MC-14659" /> + <severity value="CRITICAL"/> + <group value="backend"/> + <group value="CMSContent"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCmsPage" stepKey="firstCMSPage" /> + <createData entity="_duplicatedCMSPage" stepKey="secondCMSPage" /> + </before> + <after> + <deleteData createDataKey="firstCMSPage" stepKey="deleteFirstCMSPage" /> + <deleteData createDataKey="secondCMSPage" stepKey="deleteSecondCMSPage" /> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Go to Grid page--> + <actionGroup ref="AdminOpenCMSPagesGridActionGroup" stepKey="navigateToCMSPageGrid"/> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearPossibleGridFilters"/> + + <!--Select pages in Grid--> + <actionGroup ref="AdminSelectCMSPageInGridActionGroup" stepKey="selectFirstCMSPage"> + <argument name="identifier" value="$$firstCMSPage.identifier$$"/> + </actionGroup> + <actionGroup ref="AdminSelectCMSPageInGridActionGroup" stepKey="selectSecondCMSPage"> + <argument name="identifier" value="$$secondCMSPage.identifier$$"/> + </actionGroup> + + <!-- Disable Pages--> + <actionGroup ref="AdminCMSPageMassActionSelectActionGroup" stepKey="disablePages"> + <argument name="action" value="Disable" /> + </actionGroup> + + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSuccessMessage"> + <argument name="message" value="A total of 2 record(s) have been disabled." /> + </actionGroup> + + <!--Verify pages in Grid--> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearGridFilters"/> + <actionGroup ref="AdminGridFilterFillInputFieldActionGroup" stepKey="filterGridByFirstCmsPageIdentifier"> + <argument name="filterInputName" value="identifier" /> + <argument name="filterValue" value="$$firstCMSPage.identifier$$" /> + </actionGroup> + <actionGroup ref="AdminGridFilterApplyActionGroup" stepKey="applyFirstGridFilters"/> + <actionGroup ref="AssertCMSPageInGridActionGroup" stepKey="assertFirstCmsPageInGrid"> + <argument name="cmsPage" value="$$firstCMSPage$$" /> + </actionGroup> + + <actionGroup ref="AdminGridFilterFillInputFieldActionGroup" stepKey="filterGridBySecondCmsPageIdentifier"> + <argument name="filterInputName" value="identifier" /> + <argument name="filterValue" value="$$secondCMSPage.identifier$$" /> + </actionGroup> + <actionGroup ref="AdminGridFilterApplyActionGroup" stepKey="applySecondGridFilters"/> + <actionGroup ref="AssertCMSPageInGridActionGroup" stepKey="assertSecondCmsPageInGrid"> + <argument name="cmsPage" value="$$secondCMSPage$$" /> + </actionGroup> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearGridFiltersToIsolateTest"/> + + <!--Verify pages are disabled on Storefront--> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="goToFirstCMSPageOnStorefront"> + <argument name="identifier" value="$$firstCMSPage.identifier$$"/> + </actionGroup> + <actionGroup ref="AssertCMSPageNotFoundOnStorefrontActionGroup" stepKey="seeNotFoundErrorForFirstPage"/> + <actionGroup ref="StorefrontGoToCMSPageActionGroup" stepKey="goToSecondCMSPageOnStorefront"> + <argument name="identifier" value="$$secondCMSPage.identifier$$"/> + </actionGroup> + <actionGroup ref="AssertCMSPageNotFoundOnStorefrontActionGroup" stepKey="seeNotFoundErrorForSecondPage"/> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml index 2c351a12af72e..d4796b2ef7d43 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml @@ -11,6 +11,7 @@ <test name="StoreViewLanguageCorrectSwitchTest"> <annotations> <features value="Cms"/> + <stories value="Store view language"/> <title value="Check that Store View(language) switches correct"/> <description value="Check that Store View(language) switches correct"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php index 9d51431b26d8f..7f2ff2086df91 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php @@ -102,10 +102,6 @@ public function prepareMocksForTestExecute() ->method('filter') ->with($postData[1]) ->willReturnArgument(0); - $this->dataProcessor->expects($this->once()) - ->method('validate') - ->with($postData[1]) - ->willReturn(false); $this->messageManager->expects($this->once()) ->method('getMessages') ->with(true) @@ -122,19 +118,23 @@ public function prepareMocksForTestExecute() ->willReturn('1'); $this->cmsPage->expects($this->atLeastOnce()) ->method('getData') - ->willReturn([ - 'layout' => '1column', - 'identifier' => 'test-identifier' - ]); + ->willReturn( + [ + 'layout' => '1column', + 'identifier' => 'test-identifier' + ] + ); $this->cmsPage->expects($this->once()) ->method('setData') - ->with([ - 'layout' => '1column', - 'title' => '404 Not Found', - 'identifier' => 'no-route', - 'custom_theme' => '1', - 'custom_root_template' => '2' - ]); + ->with( + [ + 'layout' => '1column', + 'title' => '404 Not Found', + 'identifier' => 'no-route', + 'custom_theme' => '1', + 'custom_root_template' => '2' + ] + ); $this->jsonFactory->expects($this->once()) ->method('create') ->willReturn($this->resultJson); @@ -149,13 +149,15 @@ public function testExecuteWithLocalizedException() ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('LocalizedException'))); $this->resultJson->expects($this->once()) ->method('setData') - ->with([ - 'messages' => [ - '[Page ID: 1] Error message', - '[Page ID: 1] LocalizedException' - ], - 'error' => true - ]) + ->with( + [ + 'messages' => [ + '[Page ID: 1] Error message', + '[Page ID: 1] LocalizedException' + ], + 'error' => true + ] + ) ->willReturnSelf(); $this->assertSame($this->resultJson, $this->controller->execute()); @@ -170,13 +172,15 @@ public function testExecuteWithRuntimeException() ->willThrowException(new \RuntimeException(__('RuntimeException'))); $this->resultJson->expects($this->once()) ->method('setData') - ->with([ - 'messages' => [ - '[Page ID: 1] Error message', - '[Page ID: 1] RuntimeException' - ], - 'error' => true - ]) + ->with( + [ + 'messages' => [ + '[Page ID: 1] Error message', + '[Page ID: 1] RuntimeException' + ], + 'error' => true + ] + ) ->willReturnSelf(); $this->assertSame($this->resultJson, $this->controller->execute()); @@ -191,13 +195,15 @@ public function testExecuteWithException() ->willThrowException(new \Exception(__('Exception'))); $this->resultJson->expects($this->once()) ->method('setData') - ->with([ - 'messages' => [ - '[Page ID: 1] Error message', - '[Page ID: 1] Something went wrong while saving the page.' - ], - 'error' => true - ]) + ->with( + [ + 'messages' => [ + '[Page ID: 1] Error message', + '[Page ID: 1] Something went wrong while saving the page.' + ], + 'error' => true + ] + ) ->willReturnSelf(); $this->assertSame($this->resultJson, $this->controller->execute()); @@ -218,12 +224,14 @@ public function testExecuteWithoutData() ); $this->resultJson->expects($this->once()) ->method('setData') - ->with([ - 'messages' => [ - 'Please correct the data sent.' - ], - 'error' => true - ]) + ->with( + [ + 'messages' => [ + 'Please correct the data sent.' + ], + 'error' => true + ] + ) ->willReturnSelf(); $this->assertSame($this->resultJson, $this->controller->execute()); diff --git a/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php new file mode 100644 index 0000000000000..f73396230a669 --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/ValidationCompositeTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model\PageRepository; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Cms\Model\PageRepository\ValidationComposite; +use Magento\Cms\Model\PageRepository\ValidatorInterface; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\Exception\LocalizedException; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Validate behavior of the validation composite + */ +class ValidationCompositeTest extends TestCase +{ + /** + * @var PageRepositoryInterface|MockObject + */ + private $subject; + + protected function setUp() + { + /** @var PageRepositoryInterface subject */ + $this->subject = $this->createMock(PageRepositoryInterface::class); + } + + /** + * @param $validators + * @expectedException \InvalidArgumentException + * @dataProvider constructorArgumentProvider + */ + public function testConstructorValidation($validators) + { + new ValidationComposite($this->subject, $validators); + } + + public function testSaveInvokesValidatorsWithSucess() + { + $validator1 = $this->createMock(ValidatorInterface::class); + $validator2 = $this->createMock(ValidatorInterface::class); + $page = $this->createMock(PageInterface::class); + + // Assert each are called + $validator1 + ->expects($this->once()) + ->method('validate') + ->with($page); + $validator2 + ->expects($this->once()) + ->method('validate') + ->with($page); + + // Assert that the success is called + $this->subject + ->expects($this->once()) + ->method('save') + ->with($page) + ->willReturn('foo'); + + $composite = new ValidationComposite($this->subject, [$validator1, $validator2]); + $result = $composite->save($page); + + self::assertSame('foo', $result); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Oh no. That isn't right. + */ + public function testSaveInvokesValidatorsWithErrors() + { + $validator1 = $this->createMock(ValidatorInterface::class); + $validator2 = $this->createMock(ValidatorInterface::class); + $page = $this->createMock(PageInterface::class); + + // Assert the first is called + $validator1 + ->expects($this->once()) + ->method('validate') + ->with($page) + ->willThrowException(new LocalizedException(__('Oh no. That isn\'t right.'))); + + // Assert the second is NOT called + $validator2 + ->expects($this->never()) + ->method('validate'); + + // Assert that the success is NOT called + $this->subject + ->expects($this->never()) + ->method('save'); + + $composite = new ValidationComposite($this->subject, [$validator1, $validator2]); + $composite->save($page); + } + + /** + * @param $method + * @param $arg + * @dataProvider passthroughMethodDataProvider + */ + public function testPassthroughMethods($method, $arg) + { + $this->subject + ->method($method) + ->with($arg) + ->willReturn('foo'); + + $composite = new ValidationComposite($this->subject, []); + $result = $composite->{$method}($arg); + + self::assertSame('foo', $result); + } + + public function constructorArgumentProvider() + { + return [ + [[null], false], + [[''], false], + [['foo'], false], + [[new \stdClass()], false], + [[$this->createMock(ValidatorInterface::class), 'foo'], false], + ]; + } + + public function passthroughMethodDataProvider() + { + return [ + ['save', $this->createMock(PageInterface::class)], + ['getById', 1], + ['getList', $this->createMock(SearchCriteriaInterface::class)], + ['delete', $this->createMock(PageInterface::class)], + ['deleteById', 1], + ]; + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Model/PageRepository/Validator/LayoutUpdateValidatorTest.php b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/Validator/LayoutUpdateValidatorTest.php new file mode 100644 index 0000000000000..487a90bb9a185 --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/PageRepository/Validator/LayoutUpdateValidatorTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model\PageRepository\Validator; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Model\PageRepository\Validator\LayoutUpdateValidator; +use Magento\Framework\Config\Dom\ValidationException; +use Magento\Framework\Config\Dom\ValidationSchemaException; +use Magento\Framework\Config\ValidationStateInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\View\Model\Layout\Update\ValidatorFactory; +use Magento\Framework\View\Model\Layout\Update\Validator; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test cases for the layout update validator + */ +class LayoutUpdateValidatorTest extends TestCase +{ + /** + * @var Validator|MockObject + */ + private $layoutValidator; + + /** + * @var LayoutUpdateValidator + */ + private $validator; + + protected function setUp() + { + $layoutValidatorFactory = $this->createMock(ValidatorFactory::class); + $this->layoutValidator = $this->createMock(Validator::class); + $layoutValidatorState = $this->createMock(ValidationStateInterface::class); + + $layoutValidatorFactory + ->method('create') + ->with(['validationState' => $layoutValidatorState]) + ->willReturn($this->layoutValidator); + + $this->validator = new LayoutUpdateValidator($layoutValidatorFactory, $layoutValidatorState); + } + + /** + * @dataProvider validationSetDataProvider + */ + public function testValidate($data, $expectedExceptionMessage, $layoutValidatorException, $isLayoutValid = false) + { + if ($expectedExceptionMessage) { + $this->expectException(LocalizedException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + if ($layoutValidatorException) { + $this->layoutValidator + ->method('isValid') + ->with($data['getLayoutUpdateXml'] ?? $data['getCustomLayoutUpdateXml']) + ->willThrowException($layoutValidatorException); + } elseif (!empty($data['getLayoutUpdateXml'])) { + $this->layoutValidator + ->method('isValid') + ->with($data['getLayoutUpdateXml']) + ->willReturn($isLayoutValid); + } elseif (!empty($data['getCustomLayoutUpdateXml'])) { + $this->layoutValidator + ->method('isValid') + ->with($data['getCustomLayoutUpdateXml']) + ->willReturn($isLayoutValid); + } + + $page = $this->createMock(PageInterface::class); + foreach ($data as $method => $value) { + $page + ->method($method) + ->willReturn($value); + } + + self::assertNull($this->validator->validate($page)); + } + + public function validationSetDataProvider() + { + $layoutError = 'Layout update is invalid'; + $customLayoutError = 'Custom layout update is invalid'; + $validationException = new ValidationException('Invalid format'); + $schemaException = new ValidationSchemaException(__('Invalid format')); + + return [ + [['getTitle' => ''], 'Required field "title" is empty.', null], + [['getTitle' => null], 'Required field "title" is empty.', null], + [['getTitle' => false], 'Required field "title" is empty.', null], + [['getTitle' => 0], 'Required field "title" is empty.', null], + [['getTitle' => '0'], 'Required field "title" is empty.', null], + [['getTitle' => []], 'Required field "title" is empty.', null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => ''], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => null], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => false], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => 0], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => '0'], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => []], null, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => 'foo'], $layoutError, null], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => 'foo'], $layoutError, $validationException], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => 'foo'], $layoutError, $schemaException], + [['getTitle' => 'foo', 'getLayoutUpdateXml' => 'foo'], null, null, true], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => ''], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => null], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => false], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => 0], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => '0'], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => []], null, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => 'foo'], $customLayoutError, null], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => 'foo'], $customLayoutError, $validationException], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => 'foo'], $customLayoutError, $schemaException], + [['getTitle' => 'foo', 'getCustomLayoutUpdateXml' => 'foo'], null, null, true], + ]; + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Model/Plugin/ProductTest.php b/app/code/Magento/Cms/Test/Unit/Model/Plugin/ProductTest.php new file mode 100644 index 0000000000000..3e8d811623616 --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/Plugin/ProductTest.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model\Plugin; + +use Magento\Catalog\Model\Product as CatalogProduct; +use Magento\Cms\Model\Page; +use Magento\Cms\Model\Plugin\Product; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Product plugin test + */ +class ProductTest extends TestCase +{ + /** + * @var Product + */ + private $plugin; + + /** + * @var MockObject|CatalogProduct + */ + private $product; + + /** + * @var MockObject|Page + */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->product = $this->getMockBuilder(CatalogProduct::class) + ->disableOriginalConstructor() + ->setMethods(['getEntityId', 'getOrigData', 'getData', 'getCategoryIds']) + ->getMock(); + + $this->page = $this->getMockBuilder(Page::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'load']) + ->getMock(); + + $this->plugin = $objectManager->getObject( + Product::class, + [ + 'page' => $this->page + ] + ); + } + + public function testAfterGetIdentities() + { + $baseIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + ]; + $id = 12345; + $pageId = 1; + $expectedIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + Page::CACHE_TAG . '_' . $pageId, + ]; + + $this->product->method('getEntityId') + ->willReturn($id); + $this->product->method('getOrigData') + ->with('status') + ->willReturn(2); + $this->product->method('getData') + ->with('status') + ->willReturn(1); + $this->page->method('getId') + ->willReturn(1); + $this->page->method('load') + ->willReturnSelf(); + + $identities = $this->plugin->afterGetIdentities($this->product, $baseIdentities); + + $this->assertEquals($expectedIdentities, $identities); + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 6cf38324b3917..ed87c66b6e1c5 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -107,6 +107,16 @@ class StorageTest extends \PHPUnit\Framework\TestCase */ protected $objectManagerHelper; + /** + * @var \Magento\Framework\Filesystem\Io\File|\PHPUnit_Framework_MockObject_MockObject + */ + protected $ioFileMock; + + /** + * @var \Magento\Framework\Filesystem\Driver\File|\PHPUnit\Framework\MockObject\MockObject + */ + private $fileMock; + private $allowedImageExtensions = [ 'jpg' => 'image/jpg', 'jpeg' => 'image/jpeg', @@ -148,6 +158,23 @@ protected function setUp() $this->returnValue($this->directoryMock) ); + $this->fileMock = $this->createPartialMock( + \Magento\Framework\Filesystem\Driver\File::class, + ['getParentDirectory'] + ); + $this->ioFileMock = $this->createPartialMock(\Magento\Framework\Filesystem\Io\File::class, ['getPathInfo']); + $this->ioFileMock->expects( + $this->any() + )->method( + 'getPathInfo' + )->will( + $this->returnCallback( + function ($path) { + return pathinfo($path); + } + ) + ); + $this->adapterFactoryMock = $this->createMock(\Magento\Framework\Image\AdapterFactory::class); $this->imageHelperMock = $this->createPartialMock( \Magento\Cms\Helper\Wysiwyg\Images::class, @@ -223,11 +250,14 @@ protected function setUp() 'directoryDatabaseFactory' => $this->directoryDatabaseFactoryMock, 'uploaderFactory' => $this->uploaderFactoryMock, 'resizeParameters' => $this->resizeParameters, + 'extensions' => $allowedExtensions, 'dirs' => [ 'exclude' => [], 'include' => [], ], - 'extensions' => $allowedExtensions, + 'data' => [], + 'file' => $this->fileMock, + 'ioFile' => $this->ioFileMock ] ); } @@ -494,6 +524,8 @@ public function testUploadFile() ] ); + $this->fileMock->expects($this->any())->method('getParentDirectory')->willReturn($path); + $image = $this->getMockBuilder(\Magento\Catalog\Model\Product\Image::class) ->disableOriginalConstructor() ->setMethods(['open', 'keepAspectRatio', 'resize', 'save']) diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php index 6981a7983049e..4ffe4a6ad8774 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php @@ -56,10 +56,13 @@ protected function setUp() ->setMethods(['escapeHtmlAttr']) ->getMock(); - $this->blockActions = $objectManager->getObject(BlockActions::class, [ - 'context' => $context, - 'urlBuilder' => $this->urlBuilder - ]); + $this->blockActions = $objectManager->getObject( + BlockActions::class, + [ + 'context' => $context, + 'urlBuilder' => $this->urlBuilder + ] + ); $objectManager->setBackwardCompatibleProperty($this->blockActions, 'escaper', $this->escaper); } @@ -93,6 +96,7 @@ public function testPrepareDataSource() 'edit' => [ 'href' => 'test/url/edit', 'label' => __('Edit'), + '__disableTmpl' => true, ], 'delete' => [ 'href' => 'test/url/delete', @@ -102,6 +106,7 @@ public function testPrepareDataSource() 'message' => __('Are you sure you want to delete a %1 record?', $title), ], 'post' => true, + '__disableTmpl' => true, ], ], ], diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php index 32bbeed0788a3..53d8ee5220768 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php @@ -65,6 +65,7 @@ public function testPrepareItemsByPageId() 'edit' => [ 'href' => 'test/url/edit', 'label' => __('Edit'), + '__disableTmpl' => true, ], 'delete' => [ 'href' => 'test/url/delete', @@ -75,6 +76,7 @@ public function testPrepareItemsByPageId() '__disableTmpl' => true, ], 'post' => true, + '__disableTmpl' => true, ], ], ], @@ -84,7 +86,6 @@ public function testPrepareItemsByPageId() ->method('escapeHtml') ->with($title) ->willReturn($title); - // Configure mocks and object data $urlBuilderMock->expects($this->any()) ->method('getUrl') @@ -106,7 +107,6 @@ public function testPrepareItemsByPageId() ], ] ); - $model->setName($name); $items = $model->prepareDataSource($items); // Run test diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php index 6e9eef47281c0..65940c5d7b4f9 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php @@ -70,6 +70,7 @@ public function prepareDataSource(array $dataSource) ] ), 'label' => __('Edit'), + '__disableTmpl' => true, ], 'delete' => [ 'href' => $this->urlBuilder->getUrl( @@ -84,6 +85,7 @@ public function prepareDataSource(array $dataSource) 'message' => __('Are you sure you want to delete a %1 record?', $title), ], 'post' => true, + '__disableTmpl' => true, ], ]; } diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php index cfac3bbf54956..fa3756abfded4 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php @@ -27,6 +27,11 @@ class PageActions extends Column */ protected $actionUrlBuilder; + /** + * @var \Magento\Cms\ViewModel\Page\Grid\UrlBuilder + */ + private $scopeUrlBuilder; + /** * @var \Magento\Framework\UrlInterface */ @@ -50,6 +55,7 @@ class PageActions extends Column * @param array $components * @param array $data * @param string $editUrl + * @param \Magento\Cms\ViewModel\Page\Grid\UrlBuilder|null $scopeUrlBuilder */ public function __construct( ContextInterface $context, @@ -58,12 +64,15 @@ public function __construct( UrlInterface $urlBuilder, array $components = [], array $data = [], - $editUrl = self::CMS_URL_PATH_EDIT + $editUrl = self::CMS_URL_PATH_EDIT, + \Magento\Cms\ViewModel\Page\Grid\UrlBuilder $scopeUrlBuilder = null ) { $this->urlBuilder = $urlBuilder; $this->actionUrlBuilder = $actionUrlBuilder; $this->editUrl = $editUrl; parent::__construct($context, $uiComponentFactory, $components, $data); + $this->scopeUrlBuilder = $scopeUrlBuilder ?: ObjectManager::getInstance() + ->get(\Magento\Cms\ViewModel\Page\Grid\UrlBuilder::class); } /** @@ -77,7 +86,8 @@ public function prepareDataSource(array $dataSource) if (isset($item['page_id'])) { $item[$name]['edit'] = [ 'href' => $this->urlBuilder->getUrl($this->editUrl, ['page_id' => $item['page_id']]), - 'label' => __('Edit') + 'label' => __('Edit'), + '__disableTmpl' => true, ]; $title = $this->getEscaper()->escapeHtml($item['title']); $item[$name]['delete'] = [ @@ -89,16 +99,18 @@ public function prepareDataSource(array $dataSource) '__disableTmpl' => true, ], 'post' => true, + '__disableTmpl' => true, ]; } if (isset($item['identifier'])) { $item[$name]['preview'] = [ - 'href' => $this->actionUrlBuilder->getUrl( + 'href' => $this->scopeUrlBuilder->getUrl( $item['identifier'], isset($item['_first_store_id']) ? $item['_first_store_id'] : null, isset($item['store_code']) ? $item['store_code'] : null ), - 'label' => __('View') + 'label' => __('View'), + '__disableTmpl' => true, ]; } } diff --git a/app/code/Magento/Cms/ViewModel/Page/Grid/UrlBuilder.php b/app/code/Magento/Cms/ViewModel/Page/Grid/UrlBuilder.php new file mode 100644 index 0000000000000..0faf62607f5c4 --- /dev/null +++ b/app/code/Magento/Cms/ViewModel/Page/Grid/UrlBuilder.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\ViewModel\Page\Grid; + +use Magento\Framework\Url\EncoderInterface; +use Magento\Framework\App\ActionInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Url builder class used to compose dynamic urls. + */ +class UrlBuilder +{ + /** + * @var \Magento\Framework\UrlInterface + */ + private $frontendUrlBuilder; + + /** + * @var EncoderInterface + */ + private $urlEncoder; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param \Magento\Framework\UrlInterface $frontendUrlBuilder + * @param EncoderInterface $urlEncoder + * @param StoreManagerInterface $storeManager + */ + public function __construct( + \Magento\Framework\UrlInterface $frontendUrlBuilder, + EncoderInterface $urlEncoder, + StoreManagerInterface $storeManager + ) { + $this->frontendUrlBuilder = $frontendUrlBuilder; + $this->urlEncoder = $urlEncoder; + $this->storeManager = $storeManager; + } + + /** + * Get action url + * + * @param string $routePath + * @param string $scope + * @param string $store + * @return string + */ + public function getUrl($routePath, $scope, $store) + { + if ($scope) { + $this->frontendUrlBuilder->setScope($scope); + $targetUrl = $this->frontendUrlBuilder->getUrl( + $routePath, + [ + '_current' => false, + '_nosid' => true, + '_query' => [ + StoreManagerInterface::PARAM_NAME => $store + ] + ] + ); + $href = $this->frontendUrlBuilder->getUrl( + 'stores/store/switch', + [ + '_current' => false, + '_nosid' => true, + '_query' => $this->prepareRequestQuery($store, $targetUrl) + ] + ); + } else { + $href = $this->frontendUrlBuilder->getUrl( + $routePath, + [ + '_current' => false, + '_nosid' => true + ] + ); + } + + return $href; + } + + /** + * Prepare request query + * + * @param string $store + * @param string $href + * @return array + */ + private function prepareRequestQuery(string $store, string $href) : array + { + $storeView = $this->storeManager->getDefaultStoreView(); + $query = [ + StoreManagerInterface::PARAM_NAME => $store, + ActionInterface::PARAM_NAME_URL_ENCODED => $this->urlEncoder->encode($href) + ]; + if ($storeView->getCode() !== $store) { + $query['___from_store'] = $storeView->getCode(); + } + + return $query; + } +} diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index 26be4aef4f1ab..0a7a8950b2283 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -34,5 +34,5 @@ "Magento\\Cms\\": "" } }, - "version": "103.0.2" + "version": "103.0.3" } diff --git a/app/code/Magento/Cms/etc/adminhtml/di.xml b/app/code/Magento/Cms/etc/adminhtml/di.xml index 98a8ff6e9ec91..363217af4cd07 100644 --- a/app/code/Magento/Cms/etc/adminhtml/di.xml +++ b/app/code/Magento/Cms/etc/adminhtml/di.xml @@ -12,6 +12,11 @@ <argument name="frontendUrlBuilder" xsi:type="object">Magento\Framework\Url</argument> </arguments> </type> + <type name="Magento\Cms\ViewModel\Page\Grid\UrlBuilder"> + <arguments> + <argument name="frontendUrlBuilder" xsi:type="object">Magento\Framework\Url</argument> + </arguments> + </type> <type name="Magento\Cms\Model\Wysiwyg\CompositeConfigProvider"> <arguments> <argument name="variablePluginConfigProvider" xsi:type="array"> diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 66b0edf6e1eac..c8be5b7a53ab2 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -15,7 +15,7 @@ <preference for="Magento\Cms\Api\Data\PageInterface" type="Magento\Cms\Model\Page" /> <preference for="Magento\Cms\Api\Data\BlockInterface" type="Magento\Cms\Model\Block" /> <preference for="Magento\Cms\Api\BlockRepositoryInterface" type="Magento\Cms\Model\BlockRepository" /> - <preference for="Magento\Cms\Api\PageRepositoryInterface" type="Magento\Cms\Model\PageRepository" /> + <preference for="Magento\Cms\Api\PageRepositoryInterface" type="Magento\Cms\Model\PageRepository\ValidationComposite" /> <preference for="Magento\Ui\Component\Wysiwyg\ConfigInterface" type="Magento\Cms\Model\Wysiwyg\Config"/> <preference for="Magento\Cms\Api\GetUtilityPageIdentifiersInterface" type="Magento\Cms\Model\GetUtilityPageIdentifiers" /> <type name="Magento\Cms\Model\Wysiwyg\Config"> @@ -232,5 +232,15 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="cms" type="Magento\Cms\Model\Plugin\Product" sortOrder="100"/> + </type> + <type name="Magento\Cms\Model\PageRepository\ValidationComposite"> + <arguments> + <argument name="repository" xsi:type="object">Magento\Cms\Model\PageRepository</argument> + <argument name="validators" xsi:type="array"> + <item name="layout_update" xsi:type="object">Magento\Cms\Model\PageRepository\Validator\LayoutUpdateValidator</item> + </argument> + </arguments> + </type> </config> - diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content.phtml index ed376781dfaa2..5bba11cad7938 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content */ ?> diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml index 44bd7d3ba3dda..ff3d5f24dd5a1 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml @@ -4,16 +4,14 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - - /** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Files */ +/** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Files */ $_width = $block->getImagesWidth(); $_height = $block->getImagesHeight(); ?> -<?php if ($block->getFilesCount() > 0): ?> - <?php foreach ($block->getFiles() as $file): ?> +<?php if ($block->getFilesCount() > 0) : ?> + <?php foreach ($block->getFiles() as $file) : ?> <div data-row="file" class="filecnt" @@ -22,16 +20,16 @@ $_height = $block->getImagesHeight(); data-mime-type="<?= $block->escapeHtmlAttr($file->getMimeType()) ?>" > <p class="nm" style="height:<?= $block->escapeHtmlAttr($_height) ?>px;"> - <?php if ($block->getFileThumbUrl($file)):?> + <?php if ($block->getFileThumbUrl($file)) : ?> <img src="<?= $block->escapeHtmlAttr($block->getFileThumbUrl($file)) ?>" alt="<?= $block->escapeHtmlAttr($block->getFileName($file)) ?>"/> <?php endif; ?> </p> - <?php if ($block->getFileWidth($file)): ?> + <?php if ($block->getFileWidth($file)) : ?> <small><?= $block->escapeHtml($block->getFileWidth($file)) ?>x<?= $block->escapeHtml($block->getFileHeight($file)) ?> <?= $block->escapeHtml(__('px.')) ?></small><br/> <?php endif; ?> <small><?= $block->escapeHtml($block->getFileShortName($file)) ?></small> </div> <?php endforeach; ?> -<?php else: ?> +<?php else : ?> <div class="empty"><?= $block->escapeHtml(__('No files found')) ?></div> <?php endif; ?> diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml index 414d42cb45382..099efa0abdb88 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml @@ -4,12 +4,11 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Uploader */ $filters = $block->getConfig()->getFilters() ?? []; $allowedExtensions = []; +$blockHtmlId = $block->getHtmlId(); foreach ($filters as $media_type) { $allowedExtensions = array_merge($allowedExtensions, array_map(function ($fileExt) { @@ -26,13 +25,13 @@ $resizeConfig = $block->getImageUploadConfigData()->getIsResizeEnabled() : "{action: 'resize'}"; ?> -<div id="<?= $block->getHtmlId() ?>" class="uploader"> +<div id="<?= /* @noEscape */ $blockHtmlId ?>" class="uploader"> <span class="fileinput-button form-buttons"> <span><?= $block->escapeHtml(__('Upload Images')) ?></span> <input class="fileupload" type="file" name="<?= $block->escapeHtmlAttr($block->getConfig()->getFileField()) ?>" data-url="<?= $block->escapeUrl($block->getConfig()->getUrl()) ?>" multiple> </span> <div class="clear"></div> - <script type="text/x-magento-template" id="<?= $block->getHtmlId() ?>-template"> + <script type="text/x-magento-template" id="<?= /* @noEscape */ $blockHtmlId ?>-template"> <div id="<%- data.id %>" class="file-row"> <span class="file-info"><%- data.name %> (<%- data.size %>)</span> <div class="progressbar-container"> @@ -54,7 +53,7 @@ require([ var maxFileSize = <?= $block->escapeJs($block->getFileSizeService()->getMaxFileSize()) ?>, allowedExtensions = '<?= $block->escapeHtml(implode(' ', $allowedExtensions)) ?>'; - $('#<?= $block->getHtmlId() ?> .fileupload').fileupload({ + $('#<?= /* @noEscape */ $blockHtmlId ?> .fileupload').fileupload({ dataType: 'json', formData: { isAjax: 'true', @@ -64,9 +63,9 @@ require([ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, allowedExtensions: allowedExtensions, maxFileSize: maxFileSize, - dropZone: $('#<?= $block->getHtmlId() ?>').closest('[role="dialog"]'), + dropZone: $('#<?= /* @noEscape */ $blockHtmlId ?>').closest('[role="dialog"]'), add: function (e, data) { - var progressTmpl = mageTemplate('#<?= $block->getHtmlId() ?>-template'), + var progressTmpl = mageTemplate('#<?= /* @noEscape */ $blockHtmlId ?>-template'), fileSize, tmpl, validationResult; @@ -110,7 +109,7 @@ require([ } }); - $(tmpl).data('image', data).appendTo('#<?= $block->getHtmlId() ?>'); + $(tmpl).data('image', data).appendTo('#<?= /* @noEscape */ $blockHtmlId ?>'); return true; }); @@ -147,7 +146,7 @@ require([ } }); - $('#<?= $block->getHtmlId() ?> .fileupload').fileupload('option', { + $('#<?= /* @noEscape */ $blockHtmlId ?> .fileupload').fileupload('option', { process: [{ action: 'load', fileTypes: /^image\/(gif|jpeg|png)$/, diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/tree.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/tree.phtml index 4f0ba000c1d12..9603bb4f1a412 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/tree.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/tree.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Tree $block */ ?> <div class="tree-panel" > @@ -16,6 +14,6 @@ <a onclick="jQuery('[data-role=tree]').jstree('open_all');"><?= $block->escapeHtml(__('Expand All')) ?></a> </div> </div> - <div data-role="tree" data-mage-init='<?= $block->escapeHtml($this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getTreeWidgetOptions())) ?>'> + <div data-role="tree" data-mage-init='<?= $block->escapeHtml($this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getTreeWidgetOptions())) ?>'> </div> </div> diff --git a/app/code/Magento/Cms/view/adminhtml/templates/page/edit/form/renderer/content.phtml b/app/code/Magento/Cms/view/adminhtml/templates/page/edit/form/renderer/content.phtml index c773750e5d6c6..54fe4c71fb98a 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/page/edit/form/renderer/content.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/page/edit/form/renderer/content.phtml @@ -3,14 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php $_element = $block->getElement() ?> -<?php if (!$_element->getNoDisplay()): ?> +<?php if (!$_element->getNoDisplay()) : ?> <div class="cms-manage-content-actions"> - <?= trim($_element->getElementHtml()) ?> + <?= /* @noEscape */ trim($_element->getElementHtml()) ?> </div> <?php endif; ?> diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml index 4b4a1a9bfe4db..9218676a7f880 100644 --- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml +++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml @@ -123,6 +123,10 @@ <rule name="required-entry" xsi:type="boolean">true</rule> </validation> <dataType>int</dataType> + <tooltip> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <description>What is this?</description> + </tooltip> <label translate="true">Store View</label> <dataScope>store_id</dataScope> </settings> diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_form.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_form.xml index 9781675a6d917..dd58d17cbf577 100644 --- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_form.xml +++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_form.xml @@ -207,6 +207,10 @@ <rule name="required-entry" xsi:type="boolean">true</rule> </validation> <dataType>int</dataType> + <tooltip> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <description>What is this?</description> + </tooltip> <label translate="true">Store View</label> <dataScope>store_id</dataScope> </settings> @@ -285,6 +289,7 @@ <settings> <validation> <rule name="validate-date" xsi:type="boolean">true</rule> + <rule name="validate-date-range" xsi:type="string">custom_theme_from</rule> </validation> <dataType>text</dataType> <label translate="true">To</label> diff --git a/app/code/Magento/Cms/view/frontend/templates/content.phtml b/app/code/Magento/Cms/view/frontend/templates/content.phtml index d3baf14444762..f83edeb9dee61 100644 --- a/app/code/Magento/Cms/view/frontend/templates/content.phtml +++ b/app/code/Magento/Cms/view/frontend/templates/content.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= /* @noEscape */ $pageData->getPageContent() ?> diff --git a/app/code/Magento/Cms/view/frontend/templates/meta.phtml b/app/code/Magento/Cms/view/frontend/templates/meta.phtml index 3c03fa7d9ae67..40820ba37e7c6 100644 --- a/app/code/Magento/Cms/view/frontend/templates/meta.phtml +++ b/app/code/Magento/Cms/view/frontend/templates/meta.phtml @@ -3,13 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> -<?php if ($pageData->getPageMetaKeywords()): ?> +<?php if ($pageData->getPageMetaKeywords()) : ?> <meta name="keywords" content="<?= /* @noEscape */ $pageData->getPageMetaKeywords() ?>"/> <?php endif; ?> -<?php if ($pageData->getPageMetaDescription()): ?> +<?php if ($pageData->getPageMetaDescription()) : ?> <meta name="description" content="<?= /* @noEscape */ $pageData->getPageMetaDescription() ?>"/> <?php endif; ?> diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php index 8e1e770b01e9d..9ca215368ab5f 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php @@ -8,6 +8,7 @@ namespace Magento\CmsGraphQl\Model\Resolver\DataProvider; use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\GetPageByIdentifierInterface; use Magento\Cms\Api\PageRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Widget\Model\Template\FilterEmulate; @@ -18,38 +19,74 @@ class Page { /** - * @var FilterEmulate + * @var GetPageByIdentifierInterface */ - private $widgetFilter; + private $pageByIdentifier; /** * @var PageRepositoryInterface */ private $pageRepository; + /** + * @var FilterEmulate + */ + private $widgetFilter; + /** * @param PageRepositoryInterface $pageRepository * @param FilterEmulate $widgetFilter + * @param GetPageByIdentifierInterface $getPageByIdentifier */ public function __construct( PageRepositoryInterface $pageRepository, - FilterEmulate $widgetFilter + FilterEmulate $widgetFilter, + GetPageByIdentifierInterface $getPageByIdentifier ) { + $this->pageRepository = $pageRepository; $this->widgetFilter = $widgetFilter; + $this->pageByIdentifier = $getPageByIdentifier; } /** - * Get the page data + * Returns page data by page_id * * @param int $pageId * @return array * @throws NoSuchEntityException */ - public function getData(int $pageId): array + public function getDataByPageId(int $pageId): array { $page = $this->pageRepository->getById($pageId); + return $this->convertPageData($page); + } + + /** + * Returns page data by page identifier + * + * @param string $pageIdentifier + * @param int $storeId + * @return array + * @throws NoSuchEntityException + */ + public function getDataByPageIdentifier(string $pageIdentifier, int $storeId): array + { + $page = $this->pageByIdentifier->execute($pageIdentifier, $storeId); + + return $this->convertPageData($page); + } + + /** + * Convert page data + * + * @param PageInterface $page + * @return array + * @throws NoSuchEntityException + */ + private function convertPageData(PageInterface $page) + { if (false === $page->isActive()) { throw new NoSuchEntityException(); } @@ -57,7 +94,6 @@ public function getData(int $pageId): array $renderedContent = $this->widgetFilter->filter($page->getContent()); $pageData = [ - PageInterface::PAGE_ID => $page->getId(), 'url_key' => $page->getIdentifier(), PageInterface::TITLE => $page->getTitle(), PageInterface::CONTENT => $renderedContent, @@ -66,6 +102,8 @@ public function getData(int $pageId): array PageInterface::META_TITLE => $page->getMetaTitle(), PageInterface::META_DESCRIPTION => $page->getMetaDescription(), PageInterface::META_KEYWORDS => $page->getMetaKeywords(), + PageInterface::PAGE_ID => $page->getId(), + PageInterface::IDENTIFIER => $page->getIdentifier(), ]; return $pageData; } diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/Page.php b/app/code/Magento/CmsGraphQl/Model/Resolver/Page.php index 1077ab81551c2..7d03de7c4d0c3 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/Page.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/Page.php @@ -26,6 +26,7 @@ class Page implements ResolverInterface private $pageDataProvider; /** + * * @param PageDataProvider $pageDataProvider */ public function __construct( @@ -44,35 +45,21 @@ public function resolve( array $value = null, array $args = null ) { - $pageId = $this->getPageId($args); - $pageData = $this->getPageData($pageId); - - return $pageData; - } - - /** - * @param array $args - * @return int - * @throws GraphQlInputException - */ - private function getPageId(array $args): int - { - if (!isset($args['id'])) { - throw new GraphQlInputException(__('"Page id should be specified')); + if (!isset($args['id']) && !isset($args['identifier'])) { + throw new GraphQlInputException(__('"Page id/identifier should be specified')); } - return (int)$args['id']; - } + $pageData = []; - /** - * @param int $pageId - * @return array - * @throws GraphQlNoSuchEntityException - */ - private function getPageData(int $pageId): array - { try { - $pageData = $this->pageDataProvider->getData($pageId); + if (isset($args['id'])) { + $pageData = $this->pageDataProvider->getDataByPageId((int)$args['id']); + } elseif (isset($args['identifier'])) { + $pageData = $this->pageDataProvider->getDataByPageIdentifier( + (string)$args['identifier'], + (int)$context->getExtensionAttributes()->getStore()->getId() + ); + } } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index 5172f065d94fa..a1a9ba605b1d8 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-cms": "103.0.*", "magento/module-widget": "101.1.*" @@ -25,5 +25,5 @@ "Magento\\CmsGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CmsGraphQl/etc/schema.graphqls b/app/code/Magento/CmsGraphQl/etc/schema.graphqls index 09a5213fba72a..2453cb61b9a6d 100644 --- a/app/code/Magento/CmsGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CmsGraphQl/etc/schema.graphqls @@ -12,7 +12,8 @@ type StoreConfig @doc(description: "The type contains information about a store type Query { cmsPage ( - id: Int @doc(description: "Id of the CMS page") + id: Int @doc(description: "Id of the CMS page") @deprecated(reason: "The `id` is deprecated. Use `identifier` instead.") @doc(description: "The CMS page query returns information about a CMS page") + identifier: String @doc(description: "Identifier of the CMS page") ): CmsPage @resolver(class: "Magento\\CmsGraphQl\\Model\\Resolver\\Page") @doc(description: "The CMS page query returns information about a CMS page") @cache(cacheIdentity: "Magento\\CmsGraphQl\\Model\\Resolver\\Page\\Identity") cmsBlocks ( identifiers: [String] @doc(description: "Identifiers of the CMS blocks") @@ -20,6 +21,7 @@ type Query { } type CmsPage @doc(description: "CMS page defines all CMS page information") { + identifier: String @doc(description: "Identifier of the CMS page") url_key: String @doc(description: "URL key of CMS page") title: String @doc(description: "CMS page title") content: String @doc(description: "CMS page content") diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index 99a4528925dd6..24a71c2aee314 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-cms": "103.0.*", "magento/module-store": "101.0.*", @@ -24,5 +24,5 @@ "Magento\\CmsUrlRewrite\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json index b1f730ac2726e..7483fde484564 100644 --- a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-cms": "103.0.*", "magento/module-store": "101.0.*", @@ -25,5 +25,5 @@ "Magento\\CmsUrlRewriteGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Config/Console/Command/ConfigSetCommand.php b/app/code/Magento/Config/Console/Command/ConfigSetCommand.php index 6e8ca7a709b61..f278a07cc6806 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSetCommand.php +++ b/app/code/Magento/Config/Console/Command/ConfigSetCommand.php @@ -114,13 +114,13 @@ protected function configure() ), new InputOption( static::OPTION_LOCK_ENV, - 'le', + 'e', InputOption::VALUE_NONE, 'Lock value which prevents modification in the Admin (will be saved in app/etc/env.php)' ), new InputOption( static::OPTION_LOCK_CONFIG, - 'lc', + 'c', InputOption::VALUE_NONE, 'Lock and share value with other installations, prevents modification in the Admin ' . '(will be saved in app/etc/config.php)' @@ -139,8 +139,10 @@ protected function configure() /** * Creates and run appropriate processor, depending on input options. * - * {@inheritdoc} + * @param InputInterface $input + * @param OutputInterface $output * @since 101.0.0 + * @return int|null */ protected function execute(InputInterface $input, OutputInterface $output) { diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/AbstractConfig.php b/app/code/Magento/Config/Controller/Adminhtml/System/AbstractConfig.php index 69dbb06371a45..c644ec8eb15cc 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/AbstractConfig.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/AbstractConfig.php @@ -10,6 +10,7 @@ /** * System Configuration Abstract Controller + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 * diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php index 38b9a0076c3ad..91bcb632cf73b 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php @@ -224,10 +224,10 @@ public function execute() /** @var \Magento\Config\Model\Config $configModel */ $configModel = $this->_configFactory->create(['data' => $configData]); $configModel->save(); - $this->_eventManager->dispatch('admin_system_config_save', [ - 'configData' => $configData, - 'request' => $this->getRequest() - ]); + $this->_eventManager->dispatch( + 'admin_system_config_save', + ['configData' => $configData, 'request' => $this->getRequest()] + ); $this->messageManager->addSuccess(__('You saved the configuration.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { $messages = explode("\n", $e->getMessage()); diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index b5dbf97f7953d..356c6ca17da18 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -287,8 +287,8 @@ private function getField(string $sectionId, string $groupId, string $fieldId): * * @param Field $field * @param string $fieldId Need for support of clone_field feature - * @param array &$oldConfig Need for compatibility with _processGroup() - * @param array &$extraOldGroups Need for compatibility with _processGroup() + * @param array $oldConfig Need for compatibility with _processGroup() + * @param array $extraOldGroups Need for compatibility with _processGroup() * @return string */ private function getFieldPath(Field $field, string $fieldId, array &$oldConfig, array &$extraOldGroups): string @@ -337,8 +337,8 @@ private function isValueChanged(array $oldConfig, string $path, array $fieldData * @param string $sectionId * @param string $groupId * @param array $groupData - * @param array &$oldConfig - * @param array &$extraOldGroups + * @param array $oldConfig + * @param array $extraOldGroups * @return array */ private function getChangedPaths( @@ -384,8 +384,8 @@ private function getChangedPaths( * @param array $groupData * @param array $groups * @param string $sectionPath - * @param array &$extraOldGroups - * @param array &$oldConfig + * @param array $extraOldGroups + * @param array $oldConfig * @param \Magento\Framework\DB\Transaction $saveTransaction * @param \Magento\Framework\DB\Transaction $deleteTransaction * @return void @@ -546,6 +546,8 @@ public function setDataByPath($path, $value) } $section = array_shift($pathParts); + $this->setData('section', $section); + $data = [ 'fields' => [ array_pop($pathParts) => ['value' => $value], @@ -558,8 +560,8 @@ public function setDataByPath($path, $value) ], ]; } - $data['section'] = $section; - $this->addData($data); + $groups = array_replace_recursive((array) $this->getData('groups'), $data['groups']); + $this->setData('groups', $groups); } /** @@ -679,7 +681,7 @@ protected function _checkSingleStoreMode( * Get config data value * * @param string $path - * @param null|bool &$inherit + * @param null|bool $inherit * @param null|array $configData * @return \Magento\Framework\Simplexml\Element */ diff --git a/app/code/Magento/Config/Model/Config/Backend/Baseurl.php b/app/code/Magento/Config/Model/Config/Backend/Baseurl.php index 1034740fc0803..5e43e53f1b64f 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Baseurl.php +++ b/app/code/Magento/Config/Model/Config/Backend/Baseurl.php @@ -231,7 +231,7 @@ public function afterSave() /** * Get URL Validator * - * @deprecated 101.0.0 + * @deprecated 100.1.12 * @return UrlValidator */ private function getUrlValidator() diff --git a/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php b/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php index e3561261d17c7..0d56aca14fb0a 100644 --- a/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php +++ b/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php @@ -5,12 +5,11 @@ */ namespace Magento\Config\Model\Config\Reader\Source\Deployed; -use Magento\Config\Model\Config\Reader; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\DeploymentConfig; -use Magento\Config\Model\Placeholder\PlaceholderInterface; use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; use Magento\Framework\App\Config\ScopeCodeResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig; /** * Class for checking settings that defined in config file @@ -68,6 +67,12 @@ public function isReadOnly($path, $scope, $scopeCode = null) $config = $this->config->get($this->resolvePath($scope, $scopeCode) . "/" . $path); } + if (null === $config) { + $config = $this->config->get( + $this->resolvePath(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null) . "/" . $path + ); + } + return $config !== null; } @@ -78,7 +83,6 @@ public function isReadOnly($path, $scope, $scopeCode = null) * * @param string $path * @param string $scope - * @param string $scopeCode * @param string|null $scopeCode * @return string|null * @since 100.1.2 @@ -97,9 +101,11 @@ public function getPlaceholderValue($path, $scope, $scopeCode = null) */ public function getEnvValue($placeholder) { + // phpcs:disable Magento2.Security.Superglobal if ($this->placeholder->isApplicable($placeholder) && isset($_ENV[$placeholder])) { return $_ENV[$placeholder]; } + // phpcs:enable return null; } diff --git a/app/code/Magento/Config/Model/Config/Source/Email/Template.php b/app/code/Magento/Config/Model/Config/Source/Email/Template.php index 04222733418d3..e4f1ae65bcacd 100644 --- a/app/code/Magento/Config/Model/Config/Source/Email/Template.php +++ b/app/code/Magento/Config/Model/Config/Source/Email/Template.php @@ -6,6 +6,8 @@ namespace Magento\Config\Model\Config\Source\Email; /** + * Source for template + * * @api * @since 100.0.2 */ @@ -62,6 +64,12 @@ public function toOptionArray() $templateLabel = $this->_emailConfig->getTemplateLabel($templateId); $templateLabel = __('%1 (Default)', $templateLabel); array_unshift($options, ['value' => $templateId, 'label' => $templateLabel]); + array_walk( + $options, + function (&$item) { + $item['__disableTmpl'] = true; + } + ); return $options; } } diff --git a/app/code/Magento/Config/Model/Config/Structure.php b/app/code/Magento/Config/Model/Config/Structure.php index 438ce2242655d..6a03fafb363c5 100644 --- a/app/code/Magento/Config/Model/Config/Structure.php +++ b/app/code/Magento/Config/Model/Config/Structure.php @@ -368,7 +368,7 @@ protected function _getGroupFieldPathsByAttribute(array $fields, $parentPath, $a * ``` * * @return array An array of config path to config structure path map - * @since 101.0.0 + * @since 100.1.12 */ public function getFieldPaths() { diff --git a/app/code/Magento/Config/Model/Config/Structure/AbstractElement.php b/app/code/Magento/Config/Model/Config/Structure/AbstractElement.php index eeda1d4024dad..4f1f67b60e54e 100644 --- a/app/code/Magento/Config/Model/Config/Structure/AbstractElement.php +++ b/app/code/Magento/Config/Model/Config/Structure/AbstractElement.php @@ -11,6 +11,8 @@ use Magento\Framework\App\ObjectManager; /** + * Abstract element. + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/AbstractComposite.php b/app/code/Magento/Config/Model/Config/Structure/Element/AbstractComposite.php index 724772622c35b..a14114a116e78 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Element/AbstractComposite.php +++ b/app/code/Magento/Config/Model/Config/Structure/Element/AbstractComposite.php @@ -6,6 +6,9 @@ namespace Magento\Config\Model\Config\Structure\Element; /** + * Abstract Composite. + * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/Field.php b/app/code/Magento/Config/Model/Config/Structure/Element/Field.php index 0a6a600b41dd4..834b2a9e17e37 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Element/Field.php +++ b/app/code/Magento/Config/Model/Config/Structure/Element/Field.php @@ -8,6 +8,8 @@ namespace Magento\Config\Model\Config\Structure\Element; /** + * Element field. + * * @api * @since 100.0.2 */ @@ -243,6 +245,7 @@ public function getSectionId() */ public function getGroupPath() { + // phpcs:ignore Magento2.Functions.DiscouragedFunction return dirname($this->getConfigPath() ?: $this->getPath()); } diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/Group.php b/app/code/Magento/Config/Model/Config/Structure/Element/Group.php index 8003132d2b822..1ca0afa942f8d 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Element/Group.php +++ b/app/code/Magento/Config/Model/Config/Structure/Element/Group.php @@ -7,6 +7,8 @@ namespace Magento\Config\Model\Config\Structure\Element; /** + * Group element. + * * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/Section.php b/app/code/Magento/Config/Model/Config/Structure/Element/Section.php index c3d927a1d6d1b..80c029dfea2d0 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Element/Section.php +++ b/app/code/Magento/Config/Model/Config/Structure/Element/Section.php @@ -6,6 +6,8 @@ namespace Magento\Config\Model\Config\Structure\Element; /** + * Section + * * @api * @since 100.0.2 */ diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml new file mode 100644 index 0000000000000..6ed0cfe95cb94 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveConfigActionGroup"> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="clickSaveConfigBtn"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml index f05cf5be3448e..14eca30d0f730 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml @@ -22,7 +22,7 @@ <actionGroup name="SelectTopDestinationsCountry"> <arguments> - <argument name="countries" type="countryArray"/> + <argument name="countries" type="entity"/> </arguments> <selectOption selector="{{CountryOptionsSection.topDestinations}}" parameterArray="[{{countries.country}}]" stepKey="selectTopDestinationsCountry"/> <click selector="#save" stepKey="saveConfig"/> @@ -31,7 +31,7 @@ <actionGroup name="UnSelectTopDestinationsCountry"> <arguments> - <argument name="countries" type="countryArray"/> + <argument name="countries" type="entity"/> </arguments> <unselectOption selector="{{CountryOptionsSection.topDestinations}}" parameterArray="[{{countries.country}}]" stepKey="unSelectTopDestinationsCountry"/> <click selector="#save" stepKey="saveConfig"/> @@ -40,7 +40,7 @@ <actionGroup name="SelectCountriesWithRequiredRegion"> <arguments> - <argument name="countries" type="countryArray"/> + <argument name="countries" type="entity"/> </arguments> <amOnPage url="{{AdminConfigGeneralPage.url}}" stepKey="navigateToAdminConfigGeneralPage"/> <conditionalClick selector="{{StateOptionsSection.stateOptions}}" dependentSelector="{{StateOptionsSection.countriesWithRequiredRegions}}" visible="false" stepKey="expandStateOptionsTab" /> diff --git a/app/code/Magento/Config/Test/Mftf/Page/AdminCatalogSearchConfigurationPage.xml b/app/code/Magento/Config/Test/Mftf/Page/AdminCatalogSearchConfigurationPage.xml index c3fa13f59697e..d38035cd5e02e 100644 --- a/app/code/Magento/Config/Test/Mftf/Page/AdminCatalogSearchConfigurationPage.xml +++ b/app/code/Magento/Config/Test/Mftf/Page/AdminCatalogSearchConfigurationPage.xml @@ -8,5 +8,6 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCatalogSearchConfigurationPage" url="admin/system_config/edit/section/catalog/" area="admin" module="Magento_Config"> <section name="AdminCatalogSearchConfigurationSection"/> + <section name="AdminCatalogSearchEngineConfigurationSection"/> </page> </pages> diff --git a/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml b/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml index 7a62dfff8323b..688623b612c27 100644 --- a/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml +++ b/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml @@ -21,4 +21,7 @@ <page name="AdminConfigGeneralPage" url="admin/system_config/edit/section/general/" area="admin" module="Magento_Config"> <section name="GeneralSection"/> </page> + <page name="AdminConfigAdvancedAdmin" url="admin/system_config/edit/section/admin/" area="admin" module="Magento_Config"> + <section name="AdvanceAdminSection"/> + </page> </pages> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminCatalogSearchEngineConfigurationSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminCatalogSearchEngineConfigurationSection.xml new file mode 100644 index 0000000000000..570d831ce5807 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminCatalogSearchEngineConfigurationSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogSearchEngineConfigurationSection"> + <element name="searchEngineOptimization" type="button" selector="#catalog_seo-head"/> + <element name="openedEngineOptimization" type="button" selector="#catalog_seo-head.open"/> + <element name="systemValueUseCategoriesPath" type="checkbox" selector="#catalog_seo_product_use_categories_inherit"/> + <element name="selectUseCategoriesPatForProductUrls" type="select" selector="#catalog_seo_product_use_categories"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml index b5bfe9cc2ea05..fd49c1482c133 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml @@ -13,5 +13,6 @@ <element name="generalTabOpened" type="text" selector="//div[@class='admin__page-nav-title title _collapsible' and @aria-expanded='true' or @aria-expanded='1']//strong[text()='General']"/> <element name="defaultConfigButton" type="button" selector="#store-change-button" timeout="30"/> <element name="defaultConfigDropdown" type="button" selector="//ul[@class='dropdown-menu']" timeout="30"/> + <element name="fieldError" type="text" selector="label.mage-error"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml index e999dbc42a6af..e024d9e5b2e47 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml @@ -20,6 +20,15 @@ <element name="flatCatalogCategoryCheckBox" type="checkbox" selector="#catalog_frontend_flat_catalog_category_inherit"/> <element name="flatCatalogCategory" type="select" selector="#catalog_frontend_flat_catalog_category"/> <element name="flatCatalogProduct" type="select" selector="#catalog_frontend_flat_catalog_product"/> + <element name="seo" type="button" selector="#catalog_seo-head"/> + <element name="CheckIfSeoTabExpand" type="button" selector="#catalog_seo-head:not(.open)"/> + <element name="GenerateUrlRewrites" type="select" selector="#catalog_seo_generate_category_product_rewrites"/> <element name="successMessage" type="text" selector="#messages"/> </section> + <section name="GenerateUrlRewritesConfirm"> + <element name="title" type="text" selector=".modal-popup.confirm h1.modal-title"/> + <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> + <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> + </section> </sections> diff --git a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml index d007c860782aa..9bce5065317f8 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -9,11 +9,11 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ContentManagementSection"> - <element name="WYSIWYGOptions" type="button" selector="#cms_wysiwyg-head"/> + <element name="WYSIWYGOptions" type="button" selector="#cms_wysiwyg-head" timeout="60"/> <element name="CheckIfTabExpand" type="button" selector="#cms_wysiwyg-head:not(.open)"/> <element name="EnableSystemValue" type="button" selector="#cms_wysiwyg_enabled_inherit"/> <element name="EnableWYSIWYG" type="button" selector="#cms_wysiwyg_enabled"/> - <element name="SwitcherSystemValue" type="button" selector="#cms_wysiwyg_editor_inherit"/> + <element name="SwitcherSystemValue" type="button" selector="#cms_wysiwyg_editor_inherit" timeout="60"/> <element name="Switcher" type="button" selector="#cms_wysiwyg_editor" /> <element name="StaticURL" type="button" selector="#cms_wysiwyg_use_static_urls_in_catalog" /> <element name="Save" type="button" selector="#save" timeout="30"/> diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php index dc3a9a3cfa4ef..d7323b1eef0cd 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php @@ -3,14 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Config\Test\Unit\Model\Config\Reader\Source\Deployed; -use Magento\Config\Model\Config\Reader; use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; use Magento\Framework\App\Config; use Magento\Framework\App\DeploymentConfig; -use Magento\Config\Model\Placeholder\PlaceholderInterface; -use Magento\Config\Model\Placeholder\PlaceholderFactory; /** * Test class for checking settings that defined in config file @@ -71,15 +71,23 @@ public function setUp() * @param string $scopeCode * @param string|null $confValue * @param array $variables + * @param array $configMap * @param bool $expectedResult * @dataProvider isReadonlyDataProvider */ - public function testIsReadonly($path, $scope, $scopeCode, $confValue, array $variables, $expectedResult) - { - $this->placeholderMock->expects($this->once()) + public function testIsReadonly( + $path, + $scope, + $scopeCode, + $confValue, + array $variables, + array $configMap, + $expectedResult + ) { + $this->placeholderMock->expects($this->any()) ->method('isApplicable') ->willReturn(true); - $this->placeholderMock->expects($this->once()) + $this->placeholderMock->expects($this->any()) ->method('generate') ->with($path, $scope, $scopeCode) ->willReturn('SOME_PLACEHOLDER'); @@ -95,13 +103,18 @@ public function testIsReadonly($path, $scope, $scopeCode, $confValue, array $var $this->configMock->expects($this->any()) ->method('get') - ->willReturnMap([ - [ - 'system/' . $scope . "/" . ($scopeCode ? $scopeCode . '/' : '') . $path, - null, - $confValue - ], - ]); + ->willReturnMap( + array_merge( + [ + [ + 'system/' . $scope . "/" . ($scopeCode ? $scopeCode . '/' : '') . $path, + null, + $confValue + ], + ], + $configMap + ) + ); $this->assertSame($expectedResult, $this->checker->isReadOnly($path, $scope, $scopeCode)); } @@ -118,6 +131,7 @@ public function isReadonlyDataProvider() 'scopeCode' => 'myWebsite', 'confValue' => 'value', 'variables' => [], + 'configMap' => [], 'expectedResult' => true, ], [ @@ -126,6 +140,7 @@ public function isReadonlyDataProvider() 'scopeCode' => 'myWebsite', 'confValue' => null, 'variables' => ['SOME_PLACEHOLDER' => 'value'], + 'configMap' => [], 'expectedResult' => true, ], [ @@ -134,7 +149,58 @@ public function isReadonlyDataProvider() 'scopeCode' => 'myWebsite', 'confValue' => null, 'variables' => [], + 'configMap' => [], 'expectedResult' => false, + ], + [ + 'path' => 'general/web/locale', + 'scope' => 'website', + 'scopeCode' => 'myWebsite', + 'confValue' => null, + 'variables' => [], + 'configMap' => [ + [ + 'system/default/general/web/locale', + null, + 'default_value', + ], + ], + 'expectedResult' => true, + ], + [ + 'path' => 'general/web/locale', + 'scope' => 'website', + 'scopeCode' => 'myWebsite', + 'confValue' => null, + 'variables' => [], + 'configMap' => [ + [ + 'system/default/general/web/locale', + null, + 'default_value', + ], + ], + 'expectedResult' => true, + ], + [ + 'path' => 'general/web/locale', + 'scope' => 'store', + 'scopeCode' => 'myStore', + 'confValue' => null, + 'variables' => [], + 'configMap' => [ + [ + 'system/default/general/web/locale', + null, + 'default_value', + ], + [ + 'system/website/myWebsite/general/web/locale', + null, + null, + ], + ], + 'expectedResult' => true, ] ]; } diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php index b29ad244e7a81..9fabe6fef0c8e 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php @@ -6,6 +6,9 @@ namespace Magento\Config\Test\Unit\Model\Config\Source\Email; +/** + * Test class for Template. + */ class TemplateTest extends \PHPUnit\Framework\TestCase { /** @@ -76,9 +79,21 @@ public function testToOptionArray() $this->returnValue('Template New') ); $expectedResult = [ - ['value' => 'template_new', 'label' => 'Template New (Default)'], - ['value' => 'template_one', 'label' => 'Template One'], - ['value' => 'template_two', 'label' => 'Template Two'], + [ + 'value' => 'template_new', + 'label' => 'Template New (Default)', + '__disableTmpl' => true + ], + [ + 'value' => 'template_one', + 'label' => 'Template One', + '__disableTmpl' => true + ], + [ + 'value' => 'template_two', + 'label' => 'Template Two', + '__disableTmpl' => true + ], ]; $this->_model->setPath('template/new'); $this->assertEquals($expectedResult, $this->_model->toOptionArray()); diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Structure/Element/AbstractCompositeTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Structure/Element/AbstractCompositeTest.php index 57d6fa28a7822..8c54a02a491c0 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Structure/Element/AbstractCompositeTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Structure/Element/AbstractCompositeTest.php @@ -8,6 +8,9 @@ use Magento\Config\Model\Config\Structure\ElementVisibilityInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +/** + * Abstract composite test. + */ class AbstractCompositeTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index 2946fddf76119..6d995593c5d31 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-cron": "100.3.*", @@ -28,5 +28,5 @@ "Magento\\Config\\": "" } }, - "version": "101.1.2" + "version": "101.1.3" } diff --git a/app/code/Magento/Config/etc/db_schema.xml b/app/code/Magento/Config/etc/db_schema.xml index 8aeac802fbd91..1b2f7a0519411 100644 --- a/app/code/Magento/Config/etc/db_schema.xml +++ b/app/code/Magento/Config/etc/db_schema.xml @@ -15,6 +15,8 @@ default="0" comment="Config Scope Id"/> <column xsi:type="varchar" name="path" nullable="false" length="255" default="general" comment="Config Path"/> <column xsi:type="text" name="value" nullable="true" comment="Config Value"/> + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" + comment="Updated At"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="config_id"/> </constraint> diff --git a/app/code/Magento/Config/etc/db_schema_whitelist.json b/app/code/Magento/Config/etc/db_schema_whitelist.json index 850e160bc732f..51ca41d8e6af8 100644 --- a/app/code/Magento/Config/etc/db_schema_whitelist.json +++ b/app/code/Magento/Config/etc/db_schema_whitelist.json @@ -5,11 +5,12 @@ "scope": true, "scope_id": true, "path": true, - "value": true + "value": true, + "updated_at": true }, "constraint": { "PRIMARY": true, "CORE_CONFIG_DATA_SCOPE_SCOPE_ID_PATH": true } } -} \ No newline at end of file +} diff --git a/app/code/Magento/Config/view/adminhtml/templates/page/system/config/robots/reset.phtml b/app/code/Magento/Config/view/adminhtml/templates/page/system/config/robots/reset.phtml index e8082dae94d98..49a75d36fd8a5 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/page/system/config/robots/reset.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/page/system/config/robots/reset.phtml @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * @deprecated * @var $block \Magento\Backend\Block\Page\System\Config\Robots\Reset * @var $jsonHelper \Magento\Framework\Json\Helper\Data */ -$jsonHelper = $this->helper('Magento\Framework\Json\Helper\Data'); +$jsonHelper = $this->helper(\Magento\Framework\Json\Helper\Data::class); ?> <script> @@ -19,8 +17,8 @@ $jsonHelper = $this->helper('Magento\Framework\Json\Helper\Data'); 'jquery' ], function ($) { window.resetRobotsToDefault = function(){ - $('#design_search_engine_robots_custom_instructions').val(<?php - /* @escapeNotVerified */ echo $jsonHelper->jsonEncode($block->getRobotsDefaultCustomInstructions()) + $('#design_search_engine_robots_custom_instructions').val(<?= + /* @noEscape */ $jsonHelper->jsonEncode($block->getRobotsDefaultCustomInstructions()) ?>); } }); diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml index a1e26b7805d4b..4b8ebb39beb92 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -22,7 +19,8 @@ background-color: #DFF7FF!important; } </style> -<form action="<?= /* @escapeNotVerified */ $block->getSaveUrl() ?>" method="post" id="config-edit-form" enctype="multipart/form-data"> +<form action="<?= $block->escapeUrl($block->getSaveUrl()) ?>" method="post" id="config-edit-form" + enctype="multipart/form-data"> <?= $block->getBlockHtml('formkey') ?> <div class="accordion"> <?= $block->getChildHtml('form') ?> @@ -32,6 +30,8 @@ require([ "jquery", "uiRegistry", + "Magento_Ui/js/modal/confirm", + "mage/translate", "mage/mage", "prototype", "mage/adminhtml/form", @@ -159,7 +159,9 @@ require([ var elId = $el.id; $el.replace($el.outerHTML); events.each(function(event) { - Event.observe(Element.extend(document.getElementById(elId)), event.eventName, event.handler); + Event.observe( + Element.extend(document.getElementById(elId)), event.eventName, event.handler + ); }); } else { el.stopObserving('change', adminSystemConfig.onchangeSharedElement); @@ -296,7 +298,9 @@ require([ if (!scopeElement || !scopeElement.checked) { eventObj.element.enable(); eventObj.requires.each(function(required) { - adminSystemConfig.checkRequired.call(Element.extend(required), eventObj.element, eventObj.callback); + adminSystemConfig.checkRequired.call( + Element.extend(required), eventObj.element, eventObj.callback + ); }.bind(this)); } }, @@ -383,6 +387,6 @@ require([ registry.set('adminSystemConfig', adminSystemConfig); - adminSystemConfig.navigateToElement(<?php echo /* @noEscape */ $block->getConfigSearchParamsJson(); ?>); + adminSystemConfig.navigateToElement(<?= /* @noEscape */ $block->getConfigSearchParamsJson(); ?>); }); </script> diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/form/field/array.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/form/field/array.phtml index cf235d368b9bc..cf188bfeb6868 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/form/field/array.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/form/field/array.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -13,30 +10,30 @@ $_htmlId = $block->getHtmlId() ? $block->getHtmlId() : '_' . uniqid(); $_colspan = $block->isAddAfter() ? 2 : 1; ?> -<div class="design_theme_ua_regexp" id="grid<?= /* @escapeNotVerified */ $_htmlId ?>"> +<div class="design_theme_ua_regexp" id="grid<?= $block->escapeHtmlAttr($_htmlId) ?>"> <div class="admin__control-table-wrapper"> - <table class="admin__control-table" id="<?= /* @escapeNotVerified */ $block->getElement()->getId() ?>"> + <table class="admin__control-table" id="<?= $block->escapeHtmlAttr($block->getElement()->getId()) ?>"> <thead> <tr> - <?php foreach ($block->getColumns() as $columnName => $column): ?> - <th><?= /* @escapeNotVerified */ $column['label'] ?></th> - <?php endforeach;?> - <th class="col-actions" colspan="<?= /* @escapeNotVerified */ $_colspan ?>"><?= /* @escapeNotVerified */ __('Action') ?></th> + <?php foreach ($block->getColumns() as $columnName => $column) : ?> + <th><?= $block->escapeHtml($column['label']) ?></th> + <?php endforeach; ?> + <th class="col-actions" colspan="<?= (int)$_colspan ?>"><?= $block->escapeHtml(__('Action')) ?></th> </tr> </thead> <tfoot> <tr> <td colspan="<?= count($block->getColumns())+$_colspan ?>" class="col-actions-add"> - <button id="addToEndBtn<?= /* @escapeNotVerified */ $_htmlId ?>" class="action-add" title="<?= /* @escapeNotVerified */ __('Add') ?>" type="button"> - <span><?= /* @escapeNotVerified */ $block->getAddButtonLabel() ?></span> + <button id="addToEndBtn<?= $block->escapeHtmlAttr($_htmlId) ?>" class="action-add" title="<?= $block->escapeHtmlAttr(__('Add')) ?>" type="button"> + <span><?= $block->escapeHtml($block->getAddButtonLabel()) ?></span> </button> </td> </tr> </tfoot> - <tbody id="addRow<?= /* @escapeNotVerified */ $_htmlId ?>"></tbody> + <tbody id="addRow<?= $block->escapeHtmlAttr($_htmlId) ?>"></tbody> </table> </div> - <input type="hidden" name="<?= /* @escapeNotVerified */ $block->getElement()->getName() ?>[__empty]" value="" /> + <input type="hidden" name="<?= $block->escapeHtmlAttr($block->getElement()->getName()) ?>[__empty]" value="" /> <script> require([ @@ -44,23 +41,28 @@ $_colspan = $block->isAddAfter() ? 2 : 1; 'prototype' ], function (mageTemplate) { // create row creator - window.arrayRow<?= /* @escapeNotVerified */ $_htmlId ?> = { + window.arrayRow<?= $block->escapeJs($_htmlId) ?> = { // define row prototypeJS template template: mageTemplate( '<tr id="<%- _id %>">' - <?php foreach ($block->getColumns() as $columnName => $column): ?> - + '<td>' - + '<?= /* @escapeNotVerified */ $block->renderCellTemplate($columnName) ?>' - + '<\/td>' - <?php endforeach; ?> - - <?php if ($block->isAddAfter()): ?> - + '<td><button class="action-add" type="button" id="addAfterBtn<%- _id %>"><span><?= /* @escapeNotVerified */ __('Add after') ?><\/span><\/button><\/td>' - <?php endif; ?> + <?php foreach ($block->getColumns() as $columnName => $column) : ?> + + '<td>' + + '<?= $block->escapeJs($block->renderCellTemplate($columnName)) ?>' + + '<\/td>' + <?php endforeach; ?> - + '<td class="col-actions"><button onclick="arrayRow<?= /* @escapeNotVerified */ $_htmlId ?>.del(\'<%- _id %>\')" class="action-delete" type="button"><span><?= /* @escapeNotVerified */ __('Delete') ?><\/span><\/button><\/td>' - +'<\/tr>' + <?php if ($block->isAddAfter()) : ?> + + '<td><button class="action-add" type="button" id="addAfterBtn<%- _id %>"><span>' + + '<?= $block->escapeJs($block->escapeHtml(__('Add after'))) ?>' + + '<\/span><\/button><\/td>' + <?php endif; ?> + + + '<td class="col-actions"><button ' + + 'onclick="arrayRow<?= $block->escapeJs($_htmlId) ?>.del(\'<%- _id %>\')" ' + + 'class="action-delete" type="button">' + + '<span><?= $block->escapeJs($block->escapeHtml(__('Delete'))) ?><\/span><\/button><\/td>' + + '<\/tr>' ), add: function(rowData, insertAfterId) { @@ -73,56 +75,61 @@ $_colspan = $block->isAddAfter() ? 2 : 1; } else { var d = new Date(); templateValues = { - <?php foreach ($block->getColumns() as $columnName => $column): ?> - <?= /* @escapeNotVerified */ $columnName ?>: '', - 'option_extra_attrs': {}, - <?php endforeach; ?> + <?php foreach ($block->getColumns() as $columnName => $column) : ?> + <?= $block->escapeJs($columnName) ?>: '', + 'option_extra_attrs': {}, + <?php endforeach; ?> _id: '_' + d.getTime() + '_' + d.getMilliseconds() }; } // Insert new row after specified row or at the bottom if (insertAfterId) { - Element.insert($(insertAfterId), {after: this.template(templateValues)}); - } else { - Element.insert($('addRow<?= /* @escapeNotVerified */ $_htmlId ?>'), {bottom: this.template(templateValues)}); - } + Element.insert($(insertAfterId), {after: this.template(templateValues)}); + } else { + Element.insert($('addRow<?= $block->escapeJs($_htmlId) ?>'), {bottom: this.template(templateValues)}); + } - // Fill controls with data - if (rowData) { - var rowInputElementNames = Object.keys(rowData.column_values); - for (var i = 0; i < rowInputElementNames.length; i++) { - if ($(rowInputElementNames[i])) { - $(rowInputElementNames[i]).setValue(rowData.column_values[rowInputElementNames[i]]); + // Fill controls with data + if (rowData) { + var rowInputElementNames = Object.keys(rowData.column_values); + for (var i = 0; i < rowInputElementNames.length; i++) { + if ($(rowInputElementNames[i])) { + $(rowInputElementNames[i]).setValue(rowData.column_values[rowInputElementNames[i]]); + } } } - } - // Add event for {addAfterBtn} button - <?php if ($block->isAddAfter()): ?> - Event.observe('addAfterBtn' + templateValues._id, 'click', this.add.bind(this, false, templateValues._id)); + // Add event for {addAfterBtn} button + <?php if ($block->isAddAfter()) : ?> + Event.observe('addAfterBtn' + templateValues._id, 'click', this.add.bind(this, false, templateValues._id)); <?php endif; ?> - }, + }, - del: function(rowId) { - $(rowId).remove(); - } + del: function(rowId) { + $(rowId).remove(); + } } // bind add action to "Add" button in last row - Event.observe('addToEndBtn<?= /* @escapeNotVerified */ $_htmlId ?>', 'click', arrayRow<?= /* @escapeNotVerified */ $_htmlId ?>.add.bind(arrayRow<?= /* @escapeNotVerified */ $_htmlId ?>, false, false)); + Event.observe('addToEndBtn<?= $block->escapeJs($_htmlId) ?>', + 'click', + arrayRow<?= $block->escapeJs($_htmlId) ?>.add.bind( + arrayRow<?= $block->escapeJs($_htmlId) ?>, false, false + ) + ); // add existing rows <?php foreach ($block->getArrayRows() as $_rowId => $_row) { - /* @escapeNotVerified */ echo "arrayRow{$_htmlId}.add(" . $_row->toJson() . ");\n"; + echo /** @noEscape */ "arrayRow{$block->escapeJs($_htmlId)}.add(" . /** @noEscape */ $_row->toJson() . ");\n"; } ?> // Toggle the grid availability, if element is disabled (depending on scope) - <?php if ($block->getElement()->getDisabled()):?> - toggleValueElements({checked: true}, $('grid<?= /* @escapeNotVerified */ $_htmlId ?>').parentNode); - <?php endif;?> + <?php if ($block->getElement()->getDisabled()) : ?> + toggleValueElements({checked: true}, $('grid<?= $block->escapeJs($_htmlId) ?>').parentNode); + <?php endif; ?> }); </script> </div> diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/js.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/js.phtml index b703641acadb8..297687786833d 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/js.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/js.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <script> require([ @@ -71,7 +68,7 @@ originModel.prototype = { { this.reload = false; this.loader = new varienLoader(true); - this.regionsUrl = "<?= /* @escapeNotVerified */ $block->getUrl('directory/json/countryRegion') ?>"; + this.regionsUrl = "<?= $block->escapeJs($block->escapeUrl($block->getUrl('directory/json/countryRegion'))) ?>"; this.bindCountryRegionRelation(); }, @@ -146,21 +143,32 @@ originModel.prototype = { var value = this.regionElement.value; var disabled = this.regionElement.disabled; if (data.length) { - var html = '<select name="'+this.regionElement.name+'" id="'+this.regionElement.id+'" class="required-entry select" title="'+this.regionElement.title+'"'+(disabled?" disabled":"")+'>'; + var select = document.createElement('select'); + select.setAttribute('name', this.regionElement.name); + select.setAttribute('title', this.regionElement.title); + select.setAttribute('id', this.regionElement.id); + select.setAttribute('class', 'required-entry select'); + if (disabled) { + select.setAttribute('disabled', ''); + } for (var i in data) { if (data[i].label) { - html+= '<option value="'+data[i].value+'"'; - if (this.regionElement.value && (this.regionElement.value == data[i].value || this.regionElement.value == data[i].label)) { - html+= ' selected'; + var option = document.createElement('option'); + option.setAttribute('value', data[i].value); + option.innerText = data[i].label; + if (this.regionElement.value && + (this.regionElement.value == data[i].value || this.regionElement.value == data[i].label) + ) { + option.setAttribute('selected', ''); } - html+='>'+data[i].label+'<\/option>'; + select.add(option); } } - html+= '<\/select>'; var parentNode = this.regionElement.parentNode; var regionElementId = this.regionElement.id; - parentNode.innerHTML = html; + parentNode.innerHTML = select.outerHTML; + this.regionElement = $(regionElementId); } else if (this.reload) { this.clearRegionField(disabled); @@ -168,10 +176,18 @@ originModel.prototype = { } }, clearRegionField: function(disabled) { - var html = '<input type="text" name="' + this.regionElement.name + '" id="' + this.regionElement.id + '" class="input-text" title="' + this.regionElement.title + '"' + (disabled ? " disabled" : "") + '>'; + var text = document.createElement('input'); + text.setAttribute('type', 'text'); + text.setAttribute('name', this.regionElement.name); + text.setAttribute('title', this.regionElement.title); + text.setAttribute('id', this.regionElement.id); + text.setAttribute('class', 'input-text'); + if (disabled) { + text.setAttribute('disabled', ''); + } var parentNode = this.regionElement.parentNode; var regionElementId = this.regionElement.id; - parentNode.innerHTML = html; + parentNode.innerHTML = text.outerHTML; this.regionElement = $(regionElementId); } } diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/switcher.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/switcher.phtml index 6677b80076478..0d07051e6667d 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/switcher.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/switcher.phtml @@ -3,34 +3,38 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /* @var $block \Magento\Backend\Block\Template */ ?> <div class="field field-store-switcher"> - <label class="label" for="store_switcher"><?= /* @escapeNotVerified */ __('Current Configuration Scope:') ?></label> + <label class="label" for="store_switcher"><?= $block->escapeHtml(__('Current Configuration Scope:')) ?></label> <div class="control"> - <select id="store_switcher" class="system-config-store-switcher" onchange="location.href=this.options[this.selectedIndex].getAttribute('url')"> - <?php foreach ($block->getStoreSelectOptions() as $_value => $_option): ?> - <?php if (isset($_option['is_group'])): ?> - <?php if ($_option['is_close']): ?> - </optgroup> - <?php else: ?> - <optgroup label="<?= $block->escapeHtml($_option['label']) ?>" style="<?= /* @escapeNotVerified */ $_option['style'] ?>"> + <select id="store_switcher" class="system-config-store-switcher" + onchange="location.href=this.options[this.selectedIndex].getAttribute('url')"> + <?php foreach ($block->getStoreSelectOptions() as $_value => $_option) : ?> + <?php if (isset($_option['is_group'])) : ?> + <?php if ($_option['is_close']) : ?> + </optgroup> + <?php else : ?> + <optgroup label="<?= $block->escapeHtmlAttr($_option['label']) ?>" + style="<?= $block->escapeHtmlAttr($_option['style']) ?>"> + <?php endif; ?> + <?php continue ?> <?php endif; ?> - <?php continue ?> - <?php endif; ?> - <option value="<?= $block->escapeHtml($_value) ?>" url="<?= /* @escapeNotVerified */ $_option['url'] ?>" <?= $_option['selected'] ? 'selected="selected"' : '' ?> style="<?= /* @escapeNotVerified */ $_option['style'] ?>"> - <?= $block->escapeHtml($_option['label']) ?> - </option> + <option value="<?= $block->escapeHtmlAttr($_value) ?>" + url="<?= $block->escapeUrl($_option['url']) ?>" + <?= $_option['selected'] ? 'selected="selected"' : '' ?> + style="<?= $block->escapeHtmlAttr($_option['style']) ?>"> + <?= $block->escapeHtml($_option['label']) ?> + </option> <?php endforeach ?> </select> </div> <?= $block->getHintHtml() ?> - <?php if ($block->getAuthorization()->isAllowed('Magento_Backend::store')): ?> + <?php if ($block->getAuthorization()->isAllowed('Magento_Backend::store')) : ?> <div class="actions"> - <a href="<?= /* @escapeNotVerified */ $block->getUrl('*/system_store') ?>"><?= /* @escapeNotVerified */ __('Stores') ?></a> + <a href="<?= $block->escapeUrl($block->getUrl('*/system_store')) ?>"> + <?= $block->escapeHtml(__('Stores')) ?> + </a> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/tabs.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/tabs.phtml index d7000f28b5ef3..73641206a0256 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/tabs.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/tabs.phtml @@ -4,52 +4,46 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\Config\Block\System\Config\Tabs */ ?> -<?php if ($block->getTabs()): ?> - <div id="<?= /* @escapeNotVerified */ $block->getId() ?>" class="config-nav"> +<?php if ($block->getTabs()) : ?> + <div id="<?= $block->escapeHtmlAttr($block->getId()) ?>" class="config-nav"> <?php /** @var $_tab \Magento\Config\Model\Config\Structure\Element\Tab */ - foreach ($block->getTabs() as $_tab): - ?> - - <?php - $activeCollapsible = false; - foreach ($_tab->getChildren() as $_section) { - if ($block->isSectionActive($_section)) { - $activeCollapsible = true; - } + foreach ($block->getTabs() as $_tab) : + $activeCollapsible = false; + foreach ($_tab->getChildren() as $_section) { + if ($block->isSectionActive($_section)) { + $activeCollapsible = true; } - ?> + } ?> <div class="config-nav-block admin__page-nav _collapsed - <?php if ($_tab->getClass()): ?> - <?= /* @escapeNotVerified */ $_tab->getClass() ?> + <?php if ($_tab->getClass()) : ?> + <?= $block->escapeHtmlAttr($_tab->getClass()) ?> <?php endif ?>" - data-mage-init='{"collapsible":{"active": "<?= /* @escapeNotVerified */ $activeCollapsible ?>", + data-mage-init='{"collapsible":{"active": "<?= $block->escapeHtmlAttr($activeCollapsible) ?>", "openedState": "_show", "closedState": "_hide", "collapsible": true, "animate": 200}}'> <div class="admin__page-nav-title title _collapsible" data-role="title"> - <strong><?= /* @escapeNotVerified */ $_tab->getLabel() ?></strong> + <strong><?= $block->escapeHtml($_tab->getLabel()) ?></strong> </div> <ul class="admin__page-nav-items items" data-role="content"> <?php $_iterator = 1; ?> <?php /** @var $_section \Magento\Config\Model\Config\Structure\Element\Section */ - foreach ($_tab->getChildren() as $_section): ?> + foreach ($_tab->getChildren() as $_section) : ?> <li class="admin__page-nav-item item - <?= /* @escapeNotVerified */ $_section->getClass() ?> - <?php if ($block->isSectionActive($_section)): ?> _active<?php endif ?> + <?= $block->escapeHtml($_section->getClass()) ?> + <?php if ($block->isSectionActive($_section)) : ?> _active<?php endif ?> <?= $_tab->getChildren()->isLast($_section) ? ' _last' : '' ?>"> - <a href="<?= /* @escapeNotVerified */ $block->getSectionUrl($_section) ?>" + <a href="<?= $block->escapeUrl($block->getSectionUrl($_section)) ?>" class="admin__page-nav-link item-nav"> - <span><?= /* @escapeNotVerified */ $_section->getLabel() ?></span> + <span><?= $block->escapeHtml($_section->getLabel()) ?></span> </a> </li> <?php $_iterator++; ?> @@ -57,8 +51,6 @@ </ul> </div> - <?php - endforeach; - ?> + <?php endforeach; ?> </div> <?php endif; ?> diff --git a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php index 3f4565771e70b..2767e725cc9b0 100644 --- a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php @@ -596,7 +596,7 @@ protected function _parseVariations($rowData) $additionalRow['_super_attribute_position'] = $position; $additionalRows[] = $additionalRow; $additionalRow = []; - $position += 1; + $position ++; } } else { throw new LocalizedException( @@ -937,7 +937,7 @@ public function isRowValid(array $rowData, $rowNum, $isNewProduct = true) } foreach ($dataWithExtraVirtualRows as $option) { if (isset($option['_super_products_sku'])) { - if (in_array($option['_super_products_sku'], $skus)) { + if (in_array($option['_super_products_sku'], $skus, true)) { $error = true; $this->_entityModel->addRowError( sprintf( diff --git a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php index 4446f98cff515..8d75fd902e911 100644 --- a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php @@ -616,6 +616,83 @@ public function testIsRowValid() } } + public function testRowValidationForNumericalSkus() + { + // Set _attributes to avoid error in Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType. + $this->setPropertyValue($this->configurable, '_attributes', [ + 'Default' => [], + ]); + // Avoiding errors about attributes not being super + $this->setPropertyValue( + $this->configurable, + '_superAttributes', + [ + 'testattr2' => [ + 'options' => [ + 'attr2val1' => 1, + 'attr2val2' => 2, + ] + ], + ] + ); + + $rowValidationDataProvider = $this->rowValidationDataProvider(); + + // Checking that variations with duplicate sku are invalid + $result = $this->configurable->isRowValid($rowValidationDataProvider['duplicateProduct'], 0); + $this->assertFalse($result); + + // Checking that variations with SKUs that are the same when interpreted as number, + // but different when interpreted as string are valid + $result = $this->configurable->isRowValid($rowValidationDataProvider['nonDuplicateProduct'], 0); + $this->assertTrue($result); + } + + /** + * @return array + */ + public function rowValidationDataProvider() + { + return [ + 'duplicateProduct' => [ + 'sku' => 'configurableNumericalSkuDuplicateVariation', + 'store_view_code' => null, + 'attribute_set_code' => 'Default', + 'product_type' => 'configurable', + 'name' => 'Configurable Product with duplicate numerical SKUs in variations', + 'product_websites' => 'website_1', + 'configurable_variation_labels' => 'testattr2=Select Configuration', + 'configurable_variations' => 'sku=1234.1,' + . 'testattr2=attr2val1,' + . 'display=1|sku=1234.1,' + . 'testattr2=attr2val1,' + . 'display=0', + '_store' => null, + '_attribute_set' => 'Default', + '_type' => 'configurable', + '_product_websites' => 'website_1', + ], + 'nonDuplicateProduct' => [ + 'sku' => 'configurableNumericalSkuNonDuplicateVariation', + 'store_view_code' => null, + 'attribute_set_code' => 'Default', + 'product_type' => 'configurable', + 'name' => 'Configurable Product with different numerical SKUs in variations', + 'product_websites' => 'website_1', + 'configurable_variation_labels' => 'testattr2=Select Configuration', + 'configurable_variations' => 'sku=1234.10,' + . 'testattr2=attr2val1,' + . 'display=1|sku=1234.1,' + . 'testattr2=attr2val2,' + . 'display=0', + '_store' => null, + '_attribute_set' => 'Default', + '_type' => 'configurable', + '_product_websites' => 'website_1', + ] + ]; + } + /** * Set object property value. * diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index c696cc4983353..70edb12a1a736 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-catalog-import-export": "101.0.*", @@ -27,5 +27,5 @@ "Magento\\ConfigurableImportExport\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php index 4874dc8ea03ae..a7bdaaab64cb4 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php @@ -411,6 +411,7 @@ private function prepareAttributes( 'id' => $attribute->getAttributeId(), 'position' => $configurableAttributes[$attribute->getAttributeId()]['position'], 'chosen' => [], + '__disableTmpl' => true ]; foreach ($attribute->getOptions() as $option) { if (!empty($option->getValue())) { diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php index cfa2562d974f4..8b4c8d29cd98c 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php @@ -4,12 +4,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Attribute; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; +use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Json\Helper\Data; +/** + * Creates options for product attributes + */ class CreateOptions extends Action implements HttpPostActionInterface { /** @@ -20,28 +28,33 @@ class CreateOptions extends Action implements HttpPostActionInterface const ADMIN_RESOURCE = 'Magento_Catalog::products'; /** - * @var \Magento\Framework\Json\Helper\Data + * @var Data */ protected $jsonHelper; /** - * @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory + * @var AttributeFactory */ protected $attributeFactory; + /** + * @var ProductAttributeInterface[] + */ + private $attributes; + /** * @param Action\Context $context - * @param \Magento\Framework\Json\Helper\Data $jsonHelper + * @param Data $jsonHelper * @param AttributeFactory $attributeFactory */ public function __construct( Action\Context $context, - \Magento\Framework\Json\Helper\Data $jsonHelper, + Data $jsonHelper, AttributeFactory $attributeFactory ) { + parent::__construct($context); $this->jsonHelper = $jsonHelper; $this->attributeFactory = $attributeFactory; - parent::__construct($context); } /** @@ -51,7 +64,15 @@ public function __construct( */ public function execute() { - $this->getResponse()->representJson($this->jsonHelper->jsonEncode($this->saveAttributeOptions())); + try { + $output = $this->saveAttributeOptions(); + } catch (LocalizedException $e) { + $output = [ + 'error' => true, + 'message' => $e->getMessage(), + ]; + } + $this->getResponse()->representJson($this->jsonHelper->jsonEncode($output)); } /** @@ -61,31 +82,103 @@ public function execute() * @TODO Move this logic to configurable product type model * when full set of operations for attribute options during * product creation will be implemented: edit labels, remove, reorder. - * Currently only addition of options to end and removal of just added option is supported. + * Currently only addition of options is supported. + * @throws LocalizedException */ protected function saveAttributeOptions() { - $options = (array)$this->getRequest()->getParam('options'); + $attributeIds = $this->getUpdatedAttributeIds(); $savedOptions = []; - foreach ($options as $option) { - if (isset($option['label']) && isset($option['is_new'])) { - $attribute = $this->attributeFactory->create(); - $attribute->load($option['attribute_id']); - $optionsBefore = $attribute->getSource()->getAllOptions(false); - $attribute->setOption( - [ - 'value' => ['option_0' => [$option['label']]], - 'order' => ['option_0' => count($optionsBefore) + 1], - ] - ); - $attribute->save(); - $attribute = $this->attributeFactory->create(); - $attribute->load($option['attribute_id']); - $optionsAfter = $attribute->getSource()->getAllOptions(false); - $newOption = array_pop($optionsAfter); - $savedOptions[$option['id']] = $newOption['value']; + foreach ($attributeIds as $attributeId => $newOptions) { + $attribute = $this->getAttribute($attributeId); + $this->checkUnique($attribute, $newOptions); + foreach ($newOptions as $newOption) { + $lastAddedOption = $this->saveOption($attribute, $newOption); + $savedOptions[$newOption['id']] = $lastAddedOption['value']; } } + return $savedOptions; } + + /** + * Checks unique values + * + * @param ProductAttributeInterface $attribute + * @param array $newOptions + * @return void + * @throws LocalizedException + */ + private function checkUnique(ProductAttributeInterface $attribute, array $newOptions) + { + $originalOptions = $attribute->getSource()->getAllOptions(false); + $allOptions = array_merge($originalOptions, $newOptions); + $optionValues = array_map( + function ($option) { + return $option['label'] ?? null; + }, + $allOptions + ); + + $uniqueValues = array_unique(array_filter($optionValues)); + $duplicates = array_diff_assoc($optionValues, $uniqueValues); + if ($duplicates) { + throw new LocalizedException(__('The value of attribute ""%1"" must be unique', $attribute->getName())); + } + } + + /** + * Loads the product attribute by the id + * + * @param int $attributeId + * @return ProductAttributeInterface + */ + private function getAttribute(int $attributeId) + { + if (!isset($this->attributes[$attributeId])) { + $attribute = $this->attributeFactory->create(); + $this->attributes[$attributeId] = $attribute->load($attributeId); + } + + return $this->attributes[$attributeId]; + } + + /** + * Retrieve updated attribute ids with new options + * + * @return array + */ + private function getUpdatedAttributeIds() + { + $options = (array)$this->getRequest()->getParam('options'); + $updatedAttributeIds = []; + foreach ($options as $option) { + if (isset($option['label'], $option['is_new'], $option['attribute_id'])) { + $updatedAttributeIds[$option['attribute_id']][] = $option; + } + } + + return $updatedAttributeIds; + } + + /** + * Saves the option + * + * @param ProductAttributeInterface $attribute + * @param array $newOption + * @return array + */ + private function saveOption(ProductAttributeInterface $attribute, array $newOption) + { + $optionsBefore = $attribute->getSource()->getAllOptions(false); + $attribute->setOption( + [ + 'value' => ['option_0' => [$newOption['label']]], + 'order' => ['option_0' => count($optionsBefore) + 1], + ] + ); + $attribute->save(); + $optionsAfter = $attribute->getSource()->getAllOptions(false); + return array_pop($optionsAfter); + } } diff --git a/app/code/Magento/ConfigurableProduct/Model/Inventory/ParentItemProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Inventory/ParentItemProcessor.php new file mode 100644 index 0000000000000..f1567f2b196de --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Inventory/ParentItemProcessor.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Inventory; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Api\Data\ProductInterface as Product; +use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Observer\ParentItemProcessorInterface; + +/** + * Process parent stock item + */ +class ParentItemProcessor implements ParentItemProcessorInterface +{ + /** + * @var Configurable + */ + private $configurableType; + + /** + * @var StockItemCriteriaInterfaceFactory + */ + private $criteriaInterfaceFactory; + + /** + * @var StockItemRepositoryInterface + */ + private $stockItemRepository; + + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @param Configurable $configurableType + * @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory + * @param StockItemRepositoryInterface $stockItemRepository + * @param StockConfigurationInterface $stockConfiguration + */ + public function __construct( + Configurable $configurableType, + StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory, + StockItemRepositoryInterface $stockItemRepository, + StockConfigurationInterface $stockConfiguration + ) { + $this->configurableType = $configurableType; + $this->criteriaInterfaceFactory = $criteriaInterfaceFactory; + $this->stockItemRepository = $stockItemRepository; + $this->stockConfiguration = $stockConfiguration; + } + + /** + * Process parent products + * + * @param Product $product + * @return void + */ + public function process(Product $product) + { + $parentIds = $this->configurableType->getParentIdsByChild($product->getId()); + foreach ($parentIds as $productId) { + $this->processStockForParent((int)$productId); + } + } + + /** + * Change stock item for parent product depending on children stock items + * + * @param int $productId + * @return void + */ + private function processStockForParent(int $productId) + { + $criteria = $this->criteriaInterfaceFactory->create(); + $criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId()); + + $criteria->setProductsFilter($productId); + $stockItemCollection = $this->stockItemRepository->getList($criteria); + $allItems = $stockItemCollection->getItems(); + if (empty($allItems)) { + return; + } + $parentStockItem = array_shift($allItems); + + $childrenIds = $this->configurableType->getChildrenIds($productId); + $criteria->setProductsFilter($childrenIds); + $stockItemCollection = $this->stockItemRepository->getList($criteria); + $allItems = $stockItemCollection->getItems(); + + $childrenIsInStock = false; + + foreach ($allItems as $childItem) { + if ($childItem->getIsInStock() === true) { + $childrenIsInStock = true; + break; + } + } + + if ($this->isNeedToUpdateParent($parentStockItem, $childrenIsInStock)) { + $parentStockItem->setIsInStock($childrenIsInStock); + $parentStockItem->setStockStatusChangedAuto(1); + $this->stockItemRepository->save($parentStockItem); + } + } + + /** + * Check is parent item should be updated + * + * @param StockItemInterface $parentStockItem + * @param bool $childrenIsInStock + * @return bool + */ + private function isNeedToUpdateParent( + StockItemInterface $parentStockItem, + bool $childrenIsInStock + ): bool { + return $parentStockItem->getIsInStock() !== $childrenIsInStock && + ($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto()); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php index 259245ea4491f..c7217dc9df80a 100644 --- a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php +++ b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php @@ -132,7 +132,7 @@ public function addChild($sku, $childSku) throw new StateException(__("The parent product doesn't have configurable product options.")); } - $attributeIds = []; + $attributeData = []; foreach ($configurableProductOptions as $configurableProductOption) { $attributeCode = $configurableProductOption->getProductAttribute()->getAttributeCode(); if (!$child->getData($attributeCode)) { @@ -143,9 +143,11 @@ public function addChild($sku, $childSku) ) ); } - $attributeIds[] = $configurableProductOption->getAttributeId(); + $attributeData[$configurableProductOption->getAttributeId()] = [ + 'position' => $configurableProductOption->getPosition() + ]; } - $configurableOptionData = $this->getConfigurableAttributesData($attributeIds); + $configurableOptionData = $this->getConfigurableAttributesData($attributeData); /** @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory $optionFactory */ $optionFactory = $this->getOptionsFactory(); @@ -211,16 +213,16 @@ private function getOptionsFactory() /** * Get Configurable Attribute Data * - * @param int[] $attributeIds + * @param int[] $attributeData * @return array */ - private function getConfigurableAttributesData($attributeIds) + private function getConfigurableAttributesData($attributeData) { $configurableAttributesData = []; $attributeValues = []; $attributes = $this->attributeFactory->create() ->getCollection() - ->addFieldToFilter('attribute_id', $attributeIds) + ->addFieldToFilter('attribute_id', array_keys($attributeData)) ->getItems(); foreach ($attributes as $attribute) { foreach ($attribute->getOptions() as $option) { @@ -237,6 +239,7 @@ private function getConfigurableAttributesData($attributeIds) 'attribute_id' => $attribute->getId(), 'code' => $attribute->getAttributeCode(), 'label' => $attribute->getStoreLabel(), + 'position' => $attributeData[$attribute->getId()]['position'], 'values' => $attributeValues, ]; } diff --git a/app/code/Magento/ConfigurableProduct/Model/OptionRepository.php b/app/code/Magento/ConfigurableProduct/Model/OptionRepository.php index b4db0a4db5fcd..368ecad192410 100644 --- a/app/code/Magento/ConfigurableProduct/Model/OptionRepository.php +++ b/app/code/Magento/ConfigurableProduct/Model/OptionRepository.php @@ -22,6 +22,7 @@ use Magento\Store\Model\Store; /** + * Repository for performing CRUD operations for a configurable product's options. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class OptionRepository implements \Magento\ConfigurableProduct\Api\OptionRepositoryInterface @@ -112,7 +113,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function get($sku, $id) { @@ -131,7 +132,7 @@ public function get($sku, $id) } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($sku) { @@ -141,7 +142,7 @@ public function getList($sku) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(OptionInterface $option) { @@ -167,7 +168,7 @@ public function delete(OptionInterface $option) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteById($sku, $id) { @@ -184,7 +185,7 @@ public function deleteById($sku, $id) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function save($sku, OptionInterface $option) @@ -213,6 +214,16 @@ public function save($sku, OptionInterface $option) throw new \InvalidArgumentException('Incompatible product type'); } $option->setProductId($product->getData($metadata->getLinkField())); + if (!empty($option->getProductId() && !empty($option->getAttributeId()))) { + $id = $this->optionResource->getIdByProductIdAndAttributeId( + $option, + $option->getProductId(), + $option->getAttributeId() + ); + if (!empty($id)) { + $option->setId($id); + } + } } try { @@ -296,6 +307,7 @@ public function validateNewOptionData(OptionInterface $option) /** * Get MetadataPool instance + * * @return MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Plugin.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Plugin.php index 16fff36063219..cb4ac975cd582 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Plugin.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Plugin.php @@ -8,6 +8,9 @@ use Magento\Framework\Module\Manager; +/** + * Type plugin. + */ class Plugin { /** diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 1bd8ef59f0d6d..09d251519269f 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\ConfigurableProduct\Model\Product; @@ -201,12 +202,11 @@ protected function fillSimpleProductData( $keysFilter = ['item_id', 'product_id', 'stock_id', 'type_id', 'website_id']; $postData['stock_data'] = array_diff_key((array)$parentProduct->getStockData(), array_flip($keysFilter)); - if (!isset($postData['stock_data']['is_in_stock'])) { - $stockStatus = $parentProduct->getQuantityAndStockStatus(); - if (isset($stockStatus['is_in_stock'])) { - $postData['stock_data']['is_in_stock'] = $stockStatus['is_in_stock']; - } + $stockStatus = $parentProduct->getQuantityAndStockStatus(); + if (isset($stockStatus['is_in_stock'])) { + $postData['stock_data']['is_in_stock'] = $stockStatus['is_in_stock']; } + $postData = $this->processMediaGallery($product, $postData); $postData['status'] = isset($postData['status']) ? $postData['status'] diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute.php index e93c44893bf58..1b5ea4d020f8e 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute.php @@ -1,7 +1,5 @@ <?php /** - * Catalog super product attribute resource model - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -11,6 +9,9 @@ use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Store\Model\Store; +/** + * Catalog super product attribute resource model. + */ class Attribute extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** @@ -52,7 +53,7 @@ public function __construct( } /** - * Inititalize connection and define tables + * Initialize connection and define tables * * @return void */ @@ -189,8 +190,7 @@ public function deleteAttributesByProductId($productId) } /** - * @param \Magento\Framework\Model\AbstractModel $object - * @return $this + * @inheritDoc */ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object) { diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php index 7b6ca85060f99..ae591474cd13e 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php @@ -12,6 +12,7 @@ * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection @@ -26,7 +27,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection /** * @var \Magento\Catalog\Model\Product[] */ - private $products; + private $products = []; /** * Assign link table name diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index 5a172ca5eabdf..332b2b765e1c9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -79,6 +79,7 @@ <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue1"/> <fillField userInput="{{colorProductAttribute1.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute1"/> @@ -122,6 +123,26 @@ <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> </actionGroup> + <actionGroup name="createOptionsForAttribute"> + <arguments> + <argument name="attributeName" type="string" defaultValue="{{productAttributeColor.default_label}}"/> + <argument name="firstOptionName" type="string" defaultValue="option1"/> + <argument name="secondOptionName" type="string" defaultValue="option2"/> + </arguments> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickOnFilters"/> + <fillField userInput="{{attributeName}}" selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateFirstNewValue"/> + <fillField userInput="{{firstOptionName}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewFirstOption"/> + <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute"/> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateSecondNewValue"/> + <fillField userInput="{{secondOptionName}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewSecondOption"/> + <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveAttribute"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + </actionGroup> <actionGroup name="createConfigurationsForAttribute" extends="generateConfigurationsByAttributeCode"> <arguments> @@ -173,6 +194,12 @@ <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> </actionGroup> + <!--Generate and save configurable product after setting options--> + <actionGroup name="GenerateAndSaveConfiguredProductAfterSettingOptions" extends="saveConfiguredProduct"> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" before="clickOnSaveButton2" stepKey="clickOnNextButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" after="clickOnNextButton" stepKey="clickOnGenerateProductsButton"/> + </actionGroup> + <actionGroup name="addNewProductConfigurationAttribute"> <arguments> <argument name="attribute" type="entity"/> @@ -194,6 +221,7 @@ <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateFirstNewValue"/> <fillField userInput="{{firstOption.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewFirstOption"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute"/> @@ -205,7 +233,23 @@ <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnThirdNextButton"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnFourthNextButton"/> </actionGroup> - + <actionGroup name="selectCreatedAttributeAndCreateTwoOptions" extends="addNewProductConfigurationAttribute"> + <remove keyForRemoval="clickOnNewAttribute"/> + <remove keyForRemoval="waitForIFrame"/> + <remove keyForRemoval="switchToNewAttributeIFrame"/> + <remove keyForRemoval="fillDefaultLabel"/> + <remove keyForRemoval="clickOnNewAttributePanel"/> + <remove keyForRemoval="waitForSaveAttribute"/> + <remove keyForRemoval="switchOutOfIFrame"/> + <remove keyForRemoval="waitForFilters"/> + <fillField userInput="{{attribute.attribute_code}}" selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" stepKey="fillFilterAttributeCodeField"/> + <fillField userInput="{{firstOption.label}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewFirstOption"/> + <fillField userInput="{{secondOption.label}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewSecondOption"/> + <remove keyForRemoval="clickOnSelectAll"/> + <remove keyForRemoval="clickOnSecondNextButton"/> + <remove keyForRemoval="clickOnThirdNextButton"/> + <remove keyForRemoval="clickOnFourthNextButton"/> + </actionGroup> <actionGroup name="changeProductConfigurationsInGrid"> <arguments> <argument name="firstOption" type="entity"/> @@ -266,6 +310,16 @@ <selectOption userInput="{{frontend_label}}" selector="{{AdminCreateProductConfigurationsPanel.selectPriceButton}}" stepKey="selectOption"/> <fillField selector="{{AdminCreateProductConfigurationsPanel.price(label)}}" userInput="{{price}}" stepKey="enterAttributeQuantity"/> </actionGroup> + <actionGroup name="addUniqueQuantityToConfigurableProductOption"> + <arguments> + <argument name="frontend_label" type="string" defaultValue="{{productAttributeColor.default_label}}"/> + <argument name="label" type="string" defaultValue="option1"/> + <argument name="quantity" type="string" defaultValue="10"/> + </arguments> + <click selector="{{AdminCreateProductConfigurationsPanel.applyUniqueQuantityToEachSkus}}" stepKey="clickOnApplyUniqueQuantitiesToEachSku"/> + <selectOption selector="{{AdminCreateProductConfigurationsPanel.selectQuantity}}" userInput="{{frontend_label}}" stepKey="selectOption"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.applyUniqueQuantity(label)}}" userInput="{{quantity}}" stepKey="enterAttributeQuantity"/> + </actionGroup> <actionGroup name="saveConfigurableProductWithNewAttributeSet"> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveConfigurableProduct"/> @@ -302,4 +356,19 @@ <waitForPageLoad stepKey="waitForNextStepLoaded"/> <see userInput="{{title}}" selector="{{AdminProductFormConfigurationsSection.stepsWizardTitle}}" stepKey="seeStepTitle"/> </actionGroup> + <actionGroup name="AdminConfigurableProductDisableConfigurationsActionGroup"> + <arguments> + <argument name="productName" type="string" defaultValue="{{SimpleProduct.name}}"/> + </arguments> + <click selector="{{AdminProductFormConfigurationsSection.actionsBtnByProductName(productName)}}" stepKey="clickToExpandActionsSelect"/> + <click selector="{{AdminProductFormConfigurationsSection.disableProductBtn}}" stepKey="clickDisableChildProduct"/> + <see selector="{{AdminProductFormConfigurationsSection.confProductOptionStatusCell(productName)}}" userInput="Disabled" stepKey="seeConfigDisabled"/> + </actionGroup> + + <!--You are on AdminProductEditPage--> + <!--Start create configurations for attribute and fill quantity--> + <actionGroup name="StartCreateConfigurationsForAttribute" extends="generateConfigurationsByAttributeCode"> + <remove keyForRemoval="clickOnNextButton3"/> + <remove keyForRemoval="clickOnNextButton4"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminOrderConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminOrderConfigurableProductActionGroup.xml new file mode 100644 index 0000000000000..1c8fdf26735e8 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminOrderConfigurableProductActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOrderConfigureConfigurableProduct"> + <arguments> + <argument name="optionName" type="string" defaultValue="option1"/> + <argument name="productQty" type="string" defaultValue="1"/> + </arguments> + <click selector="{{AdminOrderFormItemsOrderedSection.configureButtonBySku}}" stepKey="clickConfigure"/> + <waitForPageLoad stepKey="waitForConfigurePageLoad"/> + <selectOption selector="{{AdminOrderFormConfigureProductSection.selectOption}}" userInput="{{optionName}}" stepKey="selectOption"/> + <fillField selector="{{AdminOrderFormConfigureProductSection.quantity}}" userInput="{{productQty}}" stepKey="fillProductQty"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOk"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontAddConfigurableProductToTheCartActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontAddConfigurableProductToTheCartActionGroup.xml new file mode 100644 index 0000000000000..380ffb1d0c781 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontAddConfigurableProductToTheCartActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddConfigurableProductToTheCartActionGroup"> + <arguments> + <argument name="urlKey" type="string"/> + <argument name="productAttribute" type="string"/> + <argument name="productOption" type="string" /> + <argument name="qty" type="string"/> + </arguments> + <amOnPage url="{{urlKey}}.html" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForProductFrontPageToLoad"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(productAttribute)}}" userInput="{{productOption}}" stepKey="selectOption1"/> + <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="{{qty}}" stepKey="fillProductQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> + <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeSuccessSaveMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml new file mode 100644 index 0000000000000..5d5e37ecce4ef --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName(productName)}}" stepKey="assertProductName"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(productName)}}" stepKey="AssertProductPrice"/> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(productName)}}" stepKey="moveMouseOverProduct"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(productName)}}" stepKey="AssertAddToCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml index 4c5f83ecebecf..13760f74297e7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml @@ -29,6 +29,11 @@ <data key="name" unique="suffix">Orange</data> <data key="price">99.99</data> </entity> + <entity name="productAttributeColor" type="product_attribute"> + <data key="default_label" unique="suffix">color</data> + <data key="input_type">Dropdown</data> + <data key="attribute_quantity">1</data> + </entity> <entity name="colorDefaultProductAttribute" type="product_attribute"> <data key="default_label">Color</data> <data key="input_type">Dropdown</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml index a5e74145c9fec..f3c628d002e7a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml @@ -22,7 +22,9 @@ <element name="selectAll" type="button" selector=".action-select-all"/> <element name="selectAllByAttribute" type="button" selector="//div[@data-attribute-title='{{attr}}']//button[contains(@class, 'action-select-all')]" parameterized="true"/> <element name="createNewValue" type="input" selector=".action-create-new" timeout="30"/> + <element name="attributeNameInTitle" type="input" selector="//div[contains(@class,'attribute-entity-title-block')]/div[contains(@class,'attribute-entity-title')]"/> <element name="attributeName" type="input" selector="li[data-attribute-option-title=''] .admin__field-create-new .admin__control-text"/> + <element name="attributeNameWithError" type="text" selector="//li[@data-attribute-option-title='']/div[contains(@class,'admin__field admin__field-create-new _error')]"/> <element name="saveAttribute" type="button" selector="li[data-attribute-option-title=''] .action-save" timeout="30"/> <element name="attributeCheckboxByIndex" type="input" selector="li.attribute-option:nth-of-type({{var1}}) input" parameterized="true"/> @@ -39,6 +41,9 @@ <element name="attribute3" type="input" selector="#apply-single-price-input-2"/> <element name="applySingleQuantityToEachSkus" type="radio" selector=".admin__field-label[for='apply-single-inventory-radio']" timeout="30"/> + <element name="applyUniqueQuantityToEachSkus" type="radio" selector=".admin__field-label[for='apply-unique-inventory-radio']" timeout="30"/> + <element name="selectQuantity" type="select" selector="#apply-single-price-input-qty" timeout="30"/> + <element name="applyUniqueQuantity" type="input" selector="//*[text()='{{option}}']/ancestor::div[contains(@class, 'admin__field _required')]//input[contains(@id, 'apply-qty-input')]" parameterized="true"/> <element name="applyUniqueImagesToEachSkus" type="radio" selector=".admin__field-label[for='apply-unique-images-radio']" timeout="30"/> <element name="applyUniquePricesToEachSkus" type="radio" selector=".admin__field-label[for='apply-unique-prices-radio']" timeout="30"/> <element name="selectImagesButton" type="select" selector="#apply-images-attributes" timeout="30"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml index 658e7a5fec9b3..573f1265931aa 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml @@ -16,8 +16,8 @@ <element name="visibleOnCatalogPagesOnStorefront" type="select" selector="#is_visible_on_front"/> <element name="useInProductListing" type="select" selector="#used_in_product_listing"/> <element name="usedForStoringInProductListing" type="select" selector="#used_for_sort_by"/> - <element name="storefrontPropertiesTab" selector="#front_fieldset-wrapper"/> - <element name="storefrontPropertiesTitle" selector="//span[text()='Storefront Properties']"/> + <element name="storefrontPropertiesTab" type="button" selector="#front_fieldset-wrapper"/> + <element name="storefrontPropertiesTitle" type="text" selector="//span[text()='Storefront Properties']"/> <element name="container" type="text" selector="#create_new_attribute"/> <element name="saveAttribute" type="button" selector="#save"/> <element name="newAttributeIFrame" type="iframe" selector="create_new_attribute_container"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index f6828a3b86312..1defecbc7c285 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -32,6 +32,7 @@ <element name="confProductPriceCell" type="input" selector="//*[.='Attributes']/ancestor::tr//span[contains(text(), '{{var}}')]/ancestor::tr/td[@data-index='price_container']//input" parameterized="true"/> <element name="confProductQuantityCell" type="input" selector="//*[.='Attributes']/ancestor::tr//span[contains(text(), '{{var}}')]/ancestor::tr/td[@data-index='quantity_container']//input" parameterized="true"/> <element name="confProductWeightCell" type="input" selector="//*[.='Attributes']/ancestor::tr//span[contains(text(), '{{var}}')]/ancestor::tr/td[@data-index='price_weight']//input" parameterized="true"/> + <element name="confProductOptionStatusCell" type="text" selector="//*[.='Attributes']/ancestor::tr//span[contains(text(), '{{productName}}')]/ancestor::tr/td[@data-index='status']" parameterized="true"/> <element name="confProductSkuMessage" type="text" selector="//*[@name='configurable-matrix[{{arg}}][sku]']/following-sibling::label" parameterized="true"/> <element name="variationsSkuInputByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) input[name*='sku']" type="input" parameterized="true"/> <element name="variationsSkuInputErrorByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) .admin__field-error" type="text" parameterized="true"/> @@ -44,6 +45,7 @@ <element name="productQuantity" type="input" selector=".admin__control-text[name='product[quantity_and_stock_status][qty]']"/> <element name="currentVariationsQuantityCells" type="button" selector="td[data-index='quantity_container']"/> <element name="rowByCode" type="textarea" selector="//span[contains(text(), '{{var1}}-{{var2}}')]//ancestor-or-self::tr" parameterized="true"/> + <element name="currentAttribute" type="text" selector="//fieldset[@class='admin__fieldset']/div[contains(@class, 'admin__field _disabled')]//span"/> </section> <section name="AdminConfigurableProductSelectAttributesSlideOut"> <element name="grid" type="button" selector=".admin__data-grid-wrap tbody"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 24cd9262b6742..0853d22eda7b9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -15,6 +15,7 @@ <element name="productAttributeOptions1" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/> <element name="productAttributeOptionsSelectButton" type="select" selector="#product-options-wrapper .super-attribute-select"/> <element name="productAttributeOptionsError" type="text" selector="//div[@class='mage-error']"/> + <element name="selectCustomOptionByName" type="radio" selector="//*[@class='options-list nested']//span[contains(text(), '{{value}}')]/../../input" parameterized="true"/> <!-- Parameter is the order number of the attribute on the page (1 is the newest) --> <element name="nthAttributeOnPage" type="block" selector="tr:nth-of-type({{numElement}}) .data" parameterized="true"/> <element name="stockIndication" type="block" selector=".stock" /> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml index 52443a17dfe64..a9d2f1c3379df 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml @@ -46,6 +46,7 @@ <seeElement selector="{{AdminProductFormConfigurationsSection.attributeEntityByName($$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$)}}" stepKey="seeAttribute"/> <!--Create one color option via "Create New Value" link--> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue"/> <fillField userInput="{{colorDefaultProductAttribute1.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml new file mode 100644 index 0000000000000..df6afdcfd2243 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckConfigurableProductAttributeValueUniquenessTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Configurable Product"/> + <title value="Attribute value validation (check for uniqueness) in configurable products"/> + <description value="Attribute value validation (check for uniqueness) in configurable products"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17450"/> + <useCaseId value="MAGETWO-99443"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="dropdownProductAttribute" stepKey="createProductAttribute"/> + </before> + <after> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="deleteCreatedData"/> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteConfigurableProductAndOptions"> + <argument name="product" value="$$createConfigProduct$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="logout" stepKey="logOut"/> + </after> + <!--Create configurable product--> + <comment userInput="Create configurable product" stepKey="createConfProd"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!--Go to created product page--> + <comment userInput="Go to created product page" stepKey="goToProdPage"/> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductGrid"/> + <waitForPageLoad stepKey="waitForProductPage1"/> + <actionGroup ref="filterProductGridByName2" stepKey="filterByName"> + <argument name="name" value="$$createConfigProduct.name$$"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductName"/> + <waitForPageLoad stepKey="waitForProductEditPageToLoad"/> + <!--Create configurations for the product--> + <comment userInput="Create configurations for the product" stepKey="createConfigurations"/> + <conditionalClick selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="expandConfigurationsTab1"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnCreateConfigurations1"/> + <waitForPageLoad stepKey="waitForSelectAttributesPage1"/> + <actionGroup ref="selectCreatedAttributeAndCreateTwoOptions" stepKey="selectCreatedAttributeAndCreateOptions"> + <argument name="attribute" value="dropdownProductAttribute"/> + <argument name="firstOption" value="productAttributeOption1"/> + <argument name="secondOption" value="productAttributeOption1"/> + </actionGroup> + <!--Check that system does not allow to save 2 options with same name--> + <comment userInput="Check that system does not allow to save 2 options with same name" stepKey="checkOptionNameUniqueness"/> + <seeElement selector="{{AdminCreateProductConfigurationsPanel.attributeNameWithError}}" stepKey="seeThatOptionWithSameNameIsNotSaved"/> + <!--Click next and assert error message--> + <comment userInput="Click next and assert error message" stepKey="clickNextAndAssertErrMssg"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNext"/> + <waitForPageLoad time="10" stepKey="waitForPageLoad"/> + <grabTextFrom selector="{{AdminCreateProductConfigurationsPanel.attributeNameInTitle}}" stepKey="grabErrMsg"/> + <see userInput='The value of attribute "$grabErrMsg" must be unique' stepKey="verifyAttributesValueUniqueness"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml index 2af85e1bac048..9021bf981ac13 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml @@ -104,6 +104,7 @@ <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue1"/> <fillField userInput="{{colorProductAttribute2.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml index 5633c3675ca85..ad30c91967c32 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml @@ -304,6 +304,7 @@ <amOnPage url="admin/admin/auth/logout/" stepKey="logout"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml index 001d4d17ec213..00ffe70380d18 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml @@ -239,6 +239,7 @@ <click stepKey="clickEditConfig" selector="{{AdminProductFormConfigurationsSection.createConfigurations}}"/> <waitForPageLoad stepKey="waitForEditConfig"/> <click stepKey="clickNextWizard" selector="{{AdminCreateProductConfigurationsPanel.next}}"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click stepKey="createNewValue" selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}"/> <fillField stepKey="fillNewAttribute" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" userInput="simple"/> <click stepKey="confirmNewAttribute" selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml index 39aa516077c56..3a6a20de3ed90 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml @@ -17,6 +17,9 @@ <testCaseId value="MC-88"/> <group value="ConfigurableProduct"/> <severity value="AVERAGE"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> @@ -340,6 +343,7 @@ <!-- Add a configuration option to the configurable product --> <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickEditConfigurations"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="waitCreateNewValueAppears"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue1"/> <fillField userInput="{{colorProductAttribute4.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml index 232dfe8391468..a71f51526c8ab 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml @@ -207,6 +207,6 @@ <click userInput="$$createCategory.name$$" stepKey="clickOnCategoryName"/> <waitForPageLoad stepKey="waitForPageLoad4"/> <see userInput="$$createConfigProduct.name$$" stepKey="assertProductPresent"/> - <See userInput="$$createConfigChildProduct1.price$$" stepKey="assertProductPricePresent"/> + <see userInput="$$createConfigChildProduct1.price$$" stepKey="assertProductPricePresent"/> </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml index 456be43f80b8d..c3459aec34492 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml @@ -12,6 +12,7 @@ <annotations> <features value="ConfigurableProduct"/> + <stories value="Cancel order"/> <title value="Product qunatity return after order cancel"/> <description value="Check Product qunatity return after order cancel"/> <severity value="CRITICAL"/> @@ -70,6 +71,7 @@ </actionGroup> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> <waitForPageLoad stepKey="waitForNewInvoicePageLoad"/> <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="1" stepKey="ChangeQtyToInvoice"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml index 1959551f8de2d..ac468fc92e4db 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductCategoryViewChildOnlyTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-5832"/> <group value="ConfigurableProduct"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> <!-- Create the category --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml index 57c45ee1e8997..c93b216fc89d5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -16,9 +16,6 @@ <description value="Sort by price should be correct if the apply Catalog Rule to child product of configurable product"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-69988"/> - <skip> - <issueId value="MC-5777"/> - </skip> <group value="сonfigurable_product"/> </annotations> <before> @@ -153,9 +150,9 @@ <argument name="sortBy" value="price"/> <argument name="sort" value="desc"/> </actionGroup> - <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> <!-- Delete the rule --> <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml new file mode 100644 index 0000000000000..ccbc587110d16 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml @@ -0,0 +1,208 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVisibilityOfDuplicateProductTest"> + <annotations> + <stories value="Duplicate Product"/> + <features value="ConfigurableProduct"/> + <title value="Visibility of duplicate product on the Storefront"/> + <description value="Check visibility of duplicate product on the Storefront"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-17226"/> + <useCaseId value="MAGETWO-64923"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create configurable product--> + <comment userInput="Create configurable product" stepKey="commentCreateConfigProduct"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteDuplicatedProduct"> + <argument name="product" value="$$createConfigProduct$$"/> + </actionGroup> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + <!--Delete product attributes--> + <comment userInput="Delete product attributes" stepKey="deleteCommentAttributes"/> + <actionGroup ref="deleteProductAttributeByLabel" stepKey="deleteProductAttribute"> + <argument name="ProductAttribute" value="colorProductAttribute"/> + </actionGroup> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGridFirst"/> + <actionGroup ref="deleteProductAttributeByLabel" stepKey="deleteProductSecondAttribute"> + <argument name="ProductAttribute" value="productAttributeColor"/> + </actionGroup> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGridSecond"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Create attribute and options for product--> + <comment userInput="Create attribute and options for product" stepKey="commentCreateAttributesAndOptions"/> + <amOnPage url="{{AdminProductEditPage.url($$createConfigProduct.id$$)}}" stepKey="navigateToConfigProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="addProductImage" stepKey="addImageForProduct1"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + <actionGroup ref="AdminCreateAttributeFromProductPageWithScope" stepKey="createAttributeForProduct"> + <argument name="attributeName" value="{{colorProductAttribute.default_label}}"/> + <argument name="attributeType" value="{{colorProductAttribute.input_type}}"/> + <argument name="scope" value="Global"/> + </actionGroup> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForProductPageReload"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnCreateConfigurations"/> + <waitForPageLoad stepKey="waitForFilters"/> + <actionGroup ref="createOptionsForAttribute" stepKey="createOptions"> + <argument name="attributeName" value="{{colorProductAttribute.default_label}}"/> + <argument name="firstOptionName" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="secondOptionName" value="{{colorConfigurableProductAttribute2.name}}"/> + </actionGroup> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnFirstNextButton"/> + <waitForPageLoad stepKey="waitForBulkImagesPriceQuantityPageLoad"/> + <!--Add images to configurable product attribute options--> + <comment userInput="Add images to configurable product attribute options" stepKey="commentAddImages"/> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImageToConfigurableProductOptionOne"> + <argument name="image" value="ImageUpload"/> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + </actionGroup> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImageToConfigurableProductOptionTwo"> + <argument name="image" value="ImageUpload_1"/> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + </actionGroup> + <!--Add price to product attribute options--> + <comment userInput="Add price to product attribute options" stepKey="commentAddPrice"/> + <actionGroup ref="addUniquePriceToConfigurableProductOption" stepKey="addPriceToConfigurableProductOptionFirst"> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="price" value="{{virtualProductWithRequiredFields.price}}"/> + </actionGroup> + <actionGroup ref="addUniquePriceToConfigurableProductOption" stepKey="addPriceToConfigurableProductOptionSecond"> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + <argument name="price" value="{{virtualProductWithRequiredFields.price}}"/> + </actionGroup> + <!--Add quantity to product attribute options--> + <comment userInput="Add quantity to product attribute options" stepKey="commentAddQuantity"/> + <actionGroup ref="addUniqueQuantityToConfigurableProductOption" stepKey="addUniqueQtyForFirstOption"> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="quantity" value="{{virtualProductBigQty.quantity}}"/> + </actionGroup> + <actionGroup ref="addUniqueQuantityToConfigurableProductOption" stepKey="addUniqueQtyForSecondOption"> + <argument name="frontend_label" value="{{colorProductAttribute.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + <argument name="quantity" value="{{virtualProductBigQty.quantity}}"/> + </actionGroup> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnSecondNextButton"/> + <waitForPageLoad stepKey="waitForSummaryPageLoad"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButtonToGenerateConfigs"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!--Duplicate the product--> + <comment userInput="Duplicate the product" stepKey="commentDuplicateProduct"/> + <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductForm"/> + <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="clickEnableProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="$$createConfigProduct.name$$-Updated" stepKey="fillProductName"/> + <selectOption selector="{{AdminProductFormSection.productStockStatus}}" userInput="1" stepKey="selectInStock"/> + <!--Change product image--> + <comment userInput="Change product image" stepKey="commentChangeProductImage"/> + <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> + <actionGroup ref="addProductImage" stepKey="addImageForProduct"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + <!--Disable configurations--> + <comment userInput="Disable configurations" stepKey="commentDisableConfigurations"/> + <actionGroup ref="AdminConfigurableProductDisableConfigurationsActionGroup" stepKey="disableFirstConfig"> + <argument name="productName" value="{{colorConfigurableProductAttribute1.name}}"/> + </actionGroup> + <actionGroup ref="AdminConfigurableProductDisableConfigurationsActionGroup" stepKey="disableSecondConfig"> + <argument name="productName" value="{{colorConfigurableProductAttribute2.name}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveDuplicatedProductForm"/> + <!--Create new configurations with another attribute--> + <comment userInput="Create new configurations with another attribute" stepKey="commentCreateNewConfigurations"/> + <actionGroup ref="AdminCreateAttributeFromProductPageWithScope" stepKey="createAttributeForDuplicatedProduct"> + <argument name="attributeName" value="{{productAttributeColor.default_label}}"/> + <argument name="attributeType" value="{{productAttributeColor.input_type}}"/> + <argument name="scope" value="Global"/> + </actionGroup> + <reloadPage stepKey="reloadDuplicatedProductPage"/> + <waitForPageLoad stepKey="waitForDuplicatedProductReload"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="createConfigurationsDuplicatedProduct"/> + <actionGroup ref="createOptionsForAttribute" stepKey="createOptionsForDuplicatedProduct"> + <argument name="attributeName" value="{{productAttributeColor.default_label}}"/> + <argument name="firstOptionName" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="secondOptionName" value="{{colorConfigurableProductAttribute2.name}}"/> + </actionGroup> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnFirstNextButtonForDuplicatedProduct"/> + <waitForPageLoad stepKey="waitForBulkImagesPriceQuantityPageLoadForDuplicatedProduct"/> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImgConfigProductOption1DuplicatedProduct"> + <argument name="image" value="MagentoLogo"/> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + </actionGroup> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImgConfigProductOption2DuplicatedProduct"> + <argument name="image" value="MagentoLogo"/> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + </actionGroup> + <actionGroup ref="addUniquePriceToConfigurableProductOption" stepKey="addPriceConfigProductOption1DuplicatedProduct"> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="price" value="{{virtualProductWithRequiredFields.price}}"/> + </actionGroup> + <actionGroup ref="addUniquePriceToConfigurableProductOption" stepKey="addPriceConfigProductOption2DuplicatedProduct"> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + <argument name="price" value="{{virtualProductWithRequiredFields.price}}"/> + </actionGroup> + <actionGroup ref="addUniqueQuantityToConfigurableProductOption" stepKey="addUniqueQtyOption1DuplicatedProduct"> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute1.name}}"/> + <argument name="quantity" value="{{virtualProductBigQty.quantity}}"/> + </actionGroup> + <actionGroup ref="addUniqueQuantityToConfigurableProductOption" stepKey="addUniqueQtyOption2DuplicatedProduct"> + <argument name="frontend_label" value="{{productAttributeColor.default_label}}"/> + <argument name="label" value="{{colorConfigurableProductAttribute2.name}}"/> + <argument name="quantity" value="{{virtualProductBigQty.quantity}}"/> + </actionGroup> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOn2NextButtonForDuplicatedProduct"/> + <waitForPageLoad stepKey="waitForSummaryPageLoadForDuplicatedProduct"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="generateConfigsForDuplicatedProduct"/> + <waitForPageLoad stepKey="waitForDuplicatedProductPageLoad"/> + <actionGroup ref="saveProductForm" stepKey="saveDuplicatedProduct"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Assert configurable product in category--> + <comment userInput="Assert configurable product in category" stepKey="commentAssertProductInCategoryPage"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onStorefrontCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createConfigProduct.name$$-Updated)}}" stepKey="assertProductNameInCategoryPage"/> + <!--Assert product options in Storefront product page--> + <comment userInput="Assert product options in Storefront product page" stepKey="commentAssertProductOptions"/> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.sku$$-1)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoadOnStorefront"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$createConfigProduct.name$$-Updated" stepKey="seeConfigurableProductName"/> + <see userInput="{{productAttributeColor.default_label}}" selector="{{StorefrontProductInfoMainSection.productAttributeTitle1}}" stepKey="seeColorAttributeName"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="{{colorConfigurableProductAttribute1.name}}" stepKey="selectFirstOption"/> + <see userInput="{{virtualProductWithRequiredFields.price}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="assertFirstOptionProductPrice"/> + <seeElement selector="{{StorefrontProductMediaSection.imageFile(MagentoLogo.filename)}}" stepKey="seeFirstImage"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="{{colorConfigurableProductAttribute1.name}}" stepKey="selectSecondOption"/> + <see userInput="{{virtualProductWithRequiredFields.price}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="seeSecondOptionProductPrice"/> + <seeElement selector="{{StorefrontProductMediaSection.imageFile(MagentoLogo.filename)}}" stepKey="seeSecondImage"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php index ad2fcd1e59360..c385934352ab8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php @@ -149,16 +149,19 @@ public function testAddChild() ->disableOriginalConstructor() ->setMethods(['getId', 'getData']) ->getMock(); - $extensionAttributesMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) ->disableOriginalConstructor() - ->setMethods([ - 'getConfigurableProductOptions', 'setConfigurableProductOptions', 'setConfigurableProductLinks' - ]) + ->setMethods( + [ + 'getConfigurableProductOptions', + 'setConfigurableProductOptions', + 'setConfigurableProductLinks' + ] + ) ->getMock(); $optionMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Api\Data\Option::class) ->disableOriginalConstructor() - ->setMethods(['getProductAttribute', 'getAttributeId']) + ->setMethods(['getProductAttribute', 'getPosition', 'getAttributeId']) ->getMock(); $productAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) ->disableOriginalConstructor() @@ -189,7 +192,6 @@ public function testAddChild() ->disableOriginalConstructor() ->setMethods(['getValue', 'getLabel']) ->getMock(); - $attributeCollectionMock = $this->getMockBuilder( \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class ) @@ -216,20 +218,18 @@ public function testAddChild() $productAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn('color'); $simple->expects($this->any())->method('getData')->willReturn('color'); $optionMock->expects($this->any())->method('getAttributeId')->willReturn('1'); + $optionMock->expects($this->any())->method('getPosition')->willReturn('0'); $optionsFactoryMock->expects($this->any())->method('create')->willReturn([$optionMock]); $attributeFactoryMock->expects($this->any())->method('create')->willReturn($attributeMock); $attributeMock->expects($this->any())->method('getCollection')->willReturn($attributeCollectionMock); $attributeCollectionMock->expects($this->any())->method('addFieldToFilter')->willReturnSelf(); $attributeCollectionMock->expects($this->any())->method('getItems')->willReturn([$attributeMock]); - + $attributeMock->expects($this->any())->method('getId')->willReturn(1); $attributeMock->expects($this->any())->method('getOptions')->willReturn([$attributeOptionMock]); - $extensionAttributesMock->expects($this->any())->method('setConfigurableProductOptions'); $extensionAttributesMock->expects($this->any())->method('setConfigurableProductLinks'); - $this->productRepository->expects($this->once())->method('save'); - $this->assertTrue(true, $this->object->addChild($productSku, $childSku)); } diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php index c474acbec5094..e98da6b6e4258 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php @@ -21,6 +21,8 @@ use Magento\Framework\Escaper; /** + * Loads data for product configurations. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AssociatedProducts @@ -213,6 +215,7 @@ public function getConfigurableAttributesData() 'code' => $attribute['code'], 'label' => $attribute['label'], 'position' => $attribute['position'], + '__disableTmpl' => true ]; foreach ($attribute['chosen'] as $chosenOption) { @@ -231,6 +234,7 @@ public function getConfigurableAttributesData() * * @return void * @throws \Zend_Currency_Exception + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh */ protected function prepareVariations() { @@ -261,6 +265,7 @@ protected function prepareVariations() 'id' => $attribute->getAttributeId(), 'position' => $configurableAttributes[$attribute->getAttributeId()]['position'], 'chosen' => [], + '__disableTmpl' => true ]; foreach ($attribute->getOptions() as $option) { if (!empty($option->getValue())) { @@ -270,6 +275,7 @@ protected function prepareVariations() 'id' => $option->getValue(), 'label' => $option->getLabel(), 'value' => $option->getValue(), + '__disableTmpl' => true ]; } } @@ -281,6 +287,7 @@ protected function prepareVariations() 'id' => $optionId, 'label' => $variation[$attribute->getId()]['label'], 'value' => $optionId, + '__disableTmpl' => true ]; $variationOptions[] = $variationOption; $attributes[$attribute->getAttributeId()]['chosen'][$optionId] = $variationOption; @@ -306,6 +313,7 @@ protected function prepareVariations() 'newProduct' => 0, 'attributes' => $this->getTextAttributes($variationOptions), 'thumbnail_image' => $this->imageHelper->init($product, 'product_thumbnail_image')->getUrl(), + '__disableTmpl' => true ]; $productIds[] = $product->getId(); } @@ -316,6 +324,7 @@ protected function prepareVariations() $this->productIds = $productIds; $this->productAttributes = array_values($attributes); } + //phpcs: enable /** * Get JSON string that contains attribute code and value diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 38a5661a92c1a..0457f5192582b 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-catalog": "103.0.*", @@ -41,5 +41,5 @@ "Magento\\ConfigurableProduct\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index c3ffe988b00d7..498591fc31569 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -12,6 +12,7 @@ <preference for="Magento\ConfigurableProduct\Api\Data\OptionInterface" type="Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute" /> <preference for="Magento\ConfigurableProduct\Api\Data\OptionValueInterface" type="Magento\ConfigurableProduct\Model\Product\Type\Configurable\OptionValue" /> <preference for="Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface" type="Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValue" /> + <preference for="Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurablePriceResolver" /> <preference for="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProvider" /> <preference for="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface" type="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProvider" /> <preference for="Magento\ConfigurableProduct\Model\AttributeOptionProviderInterface" type="Magento\ConfigurableProduct\Model\AttributeOptionProvider" /> @@ -255,4 +256,11 @@ </argument> </arguments> </type> + <type name="Magento\CatalogInventory\Observer\SaveInventoryDataObserver"> + <arguments> + <argument name="parentItemProcessorPool" xsi:type="array"> + <item name="configurable" xsi:type="object"> Magento\ConfigurableProduct\Model\Inventory\ParentItemProcessor</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml index 110defd5248b9..9307da21e6659 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml @@ -8,7 +8,7 @@ <script> (function ($) { - var data = <?= /* @escapeNotVerified */ $block->getAttributesBlockJson() ?>; + var data = <?= /* @noEscape */ $block->getAttributesBlockJson() ?>; var set = data.set || {id: $('#attribute_set_id').val()}; if (data.tab == 'variations') { $('[data-role=product-variations-matrix]').trigger('add', data.attribute); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/set/js.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/set/js.phtml index 9c4612c972d96..5f49d5eb47442 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/set/js.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/set/js.phtml @@ -5,8 +5,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - ?> <script> require([ diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml index ecc95cbe3d48f..1166adca97255 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml @@ -3,34 +3,32 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - - ?> +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis +?> <?php /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Composite\Fieldset\Configurable */ ?> <?php $_product = $block->getProduct(); ?> <?php $_attributes = $block->decorateArray($block->getAllowAttributes()); ?> -<?php $_skipSaleableCheck = $this->helper('Magento\Catalog\Helper\Product')->getSkipSaleableCheck(); ?> -<?php if (($_product->isSaleable() || $_skipSaleableCheck) && count($_attributes)):?> +<?php $_skipSaleableCheck = $this->helper(Magento\Catalog\Helper\Product::class)->getSkipSaleableCheck(); ?> +<?php if (($_product->isSaleable() || $_skipSaleableCheck) && count($_attributes)) :?> <fieldset id="catalog_product_composite_configure_fields_configurable" class="fieldset admin__fieldset"> <legend class="legend admin__legend"> - <span><?= /* @escapeNotVerified */ __('Associated Products') ?></span> + <span><?= $block->escapeHtml(__('Associated Products')) ?></span> </legend> <div class="product-options fieldset admin__fieldset"> - <?php foreach ($_attributes as $_attribute): ?> + <?php foreach ($_attributes as $_attribute) : ?> <div class="field admin__field _required required"> <label class="label admin__field-label"><?= $block->escapeHtml($_attribute->getProductAttribute()->getStoreLabel($_product->getStoreId())); - ?></label> + ?></label> <div class="control admin__field-control <?php - if ($_attribute->getDecoratedIsLast()): - ?> last<?php + if ($_attribute->getDecoratedIsLast()) : + ?> last<?php endif; ?>"> - <select name="super_attribute[<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>]" - id="attribute<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>" + <select name="super_attribute[<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>]" + id="attribute<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>" class="admin__control-select required-entry super-attribute-select"> - <option><?= /* @escapeNotVerified */ __('Choose an Option...') ?></option> + <option><?= $block->escapeHtml(__('Choose an Option...')) ?></option> </select> </div> </div> @@ -43,7 +41,7 @@ require([ "Magento_Catalog/catalog/product/composite/configure" ], function(){ - var config = <?= /* @escapeNotVerified */ $block->getJsonConfig() ?>; + var config = <?= /* @noEscape */ $block->getJsonConfig() ?>; if (window.productConfigure) { config.containerId = window.productConfigure.blockFormFields.id; if (window.productConfigure.restorePhase) { diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/attributes_values.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/attributes_values.phtml index cc25474049190..e996df8260719 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/attributes_values.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/attributes_values.phtml @@ -4,18 +4,16 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\AttributeValues */ ?> <div data-bind="scope: '<?= /* @noEscape */ $block->getComponentName() ?>'"> <h2 class="steps-wizard-title"><?= $block->escapeHtml( - __('Step 2: Attribute Values') - ); ?></h2> + __('Step 2: Attribute Values') + ); ?></h2> <div class="steps-wizard-info"> <span><?= $block->escapeHtml( - __('Select values from each attribute to include in this product. Each unique combination of values creates a unique product SKU.') - );?></span> + __('Select values from each attribute to include in this product. Each unique combination of values creates a unique product SKU.') + );?></span> </div> <div data-bind="foreach: attributes, sortableList: attributes"> @@ -41,24 +39,24 @@ data-bind="click: $parent.selectAllAttributes" title="<?= $block->escapeHtml(__('Select All')) ?>"> <span><?= $block->escapeHtml( - __('Select All') - ); ?></span> + __('Select All') + ); ?></span> </button> <button type="button" class="action-deselect-all action-tertiary" data-bind="click: $parent.deSelectAllAttributes" title="<?= $block->escapeHtml(__('Deselect All')) ?>"> <span><?= $block->escapeHtml( - __('Deselect All') - ); ?></span> + __('Deselect All') + ); ?></span> </button> <button type="button" class="action-remove-all action-tertiary" data-bind="click: $parent.removeAttribute.bind($parent)" title="<?= $block->escapeHtml(__('Remove Attribute')) ?>"> <span><?= $block->escapeHtml( - __('Remove Attribute') - ); ?></span> + __('Remove Attribute') + ); ?></span> </button> </div> </div> @@ -74,7 +72,7 @@ <label data-bind="text: label, visible: label, attr:{for:id}" class="admin__field-label"></label> </div> - <div class="admin__field admin__field-create-new" data-bind="visible: !label"> + <div class="admin__field admin__field-create-new" data-bind="attr:{'data-role':id}, visible: !label"> <div class="admin__field-control"> <input class="admin__control-text" name="label" @@ -87,8 +85,8 @@ data-action="save" data-bind="click: $parents[1].saveOption.bind($parent)"> <span><?= $block->escapeHtml( - __('Save Option') - ); ?></span> + __('Save Option') + ); ?></span> </button> <button type="button" class="action-remove" @@ -96,8 +94,8 @@ data-action="remove" data-bind="click: $parents[1].removeOption.bind($parent)"> <span><?= $block->escapeHtml( - __('Remove Option') - ); ?></span> + __('Remove Option') + ); ?></span> </button> </div> </li> @@ -108,8 +106,8 @@ data-action="addOption" data-bind="click: $parent.createOption, visible: canCreateOption"> <span><?= $block->escapeHtml( - __('Create New Value') - ); ?></span> + __('Create New Value') + ); ?></span> </button> </div> </div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml index 9d144b0f569e0..a792a35da8051 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml @@ -3,24 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Bulk */ ?> <div data-bind="scope: '<?= /* @noEscape */ $block->getComponentName() ?>'" data-role="bulk-step"> <h2 class="steps-wizard-title"><?= $block->escapeHtml(__('Step 3: Bulk Images, Price and Quantity')) ?></h2> <div class="steps-wizard-info"> - <?= /* @escapeNotVerified */ __('Based on your selections %1 new products will be created. Use this step to customize images and price for your new products.', '<span class="new-products-count" data-bind="text:countVariations"></span>') ?> + <?= /* @noEscape */ __('Based on your selections %1 new products will be created. Use this step to customize images and price for your new products.', '<span class="new-products-count" data-bind="text:countVariations"></span>') ?> </div> <div data-bind="with: sections().images" class="steps-wizard-section"> <div data-role="section"> <div class="steps-wizard-section-title"> - <span><?= $block->escapeHtml( - __('Images') - ); ?></span> + <span><?= $block->escapeHtml(__('Images')); ?></span> </div> <ul class="steps-wizard-section-list"> @@ -32,9 +28,7 @@ value="single" data-bind="checked:type"> <label for="apply-single-set-radio" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Apply single set of images to all SKUs') - ); ?></span> + <span><?= $block->escapeHtml(__('Apply single set of images to all SKUs')); ?></span> </label> </div> </li> @@ -71,10 +65,7 @@ <div data-role="gallery" class="gallery" data-images="[]" - data-types="<?= $block->escapeHtml( - $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes()) - ) ?>" - > + data-types="<?= $block->escapeHtml($this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getImageTypes())) ?>"> <div class="image image-placeholder"> <div data-role="uploader" class="uploader"> <div class="image-browse"> @@ -92,15 +83,12 @@ </div> </div> - <?php foreach ($block->getImageTypes() as $typeData): - ?> + <?php foreach ($block->getImageTypes() as $typeData) : ?> <input name="<?= $block->escapeHtml($typeData['name']) ?>" class="image-<?= $block->escapeHtml($typeData['code']) ?>" type="hidden" value="<?= $block->escapeHtml($typeData['value']) ?>"/> - <?php - endforeach; - ?> + <?php endforeach; ?> <script data-template="uploader" type="text/x-magento-template"> <div id="<%- data.id %>" class="file-row"> @@ -155,19 +143,12 @@ </div> </div> <ul class="item-roles" data-role="roles-labels"> - <?php - foreach ($block->getMediaAttributes() as $attribute): - ?> - <li data-role-code="<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>" class="item-role item-role-<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>"> + <?php foreach ($block->getMediaAttributes() as $attribute) :?> + <li data-role-code="<?= $block->escapeHtml($attribute->getAttributeCode()) ?>" + class="item-role item-role-<?= $block->escapeHtml($attribute->getAttributeCode()) ?>"> <?= /* @noEscape */ $attribute->getFrontendLabel() ?> </li> - <?php - endforeach; - ?> + <?php endforeach; ?> </ul> </div> </script> @@ -229,9 +210,7 @@ <div class="admin__field field-image-role"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Role') - ); ?></span> + <span><?= $block->escapeHtml(__('Role')); ?></span> </label> <div class="admin__field-control"> <ul class="multiselect-alt"> @@ -243,42 +222,28 @@ <input class="image-type" data-role="type-selector" type="checkbox" - value="<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>" + value="<?= $block->escapeHtml($attribute->getAttributeCode()) ?>" /> - <?= $block->escapeHtml( - $attribute->getFrontendLabel() - ); ?> + <?= $block->escapeHtml($attribute->getFrontendLabel()); ?> </label> </li> - <?php - endforeach; - ?> + <?php endforeach; ?> </ul> </div> </div> <div class="admin__field admin__field-inline field-image-size" data-role="size"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Image Size') - ); ?></span> + <span><?= $block->escapeHtml(__('Image Size')); ?></span> </label> - <div class="admin__field-value" data-message="<?= $block->escapeHtml( - __('{size}') - );?>"></div> + <div class="admin__field-value" data-message="<?= $block->escapeHtml(__('{size}'));?>"></div> </div> <div class="admin__field admin__field-inline field-image-resolution" data-role="resolution"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Image Resolution') - ); ?></span> + <span><?= $block->escapeHtml(__('Image Resolution')); ?></span> </label> - <div class="admin__field-value" data-message="<?= $block->escapeHtml( - __('{width}^{height} px') - );?>"></div> + <div class="admin__field-value" data-message="<?= $block->escapeHtml(__('{width}^{height} px'));?>"></div> </div> <div class="admin__field field-image-hide"> @@ -293,9 +258,7 @@ <% if (data.disabled == 1) { %>checked="checked"<% } %> /> <label for="hide-from-product-page" class="admin__field-label"> - <?= $block->escapeHtml( - __('Hide from Product Page') - ); ?> + <?= $block->escapeHtml(__('Hide from Product Page')); ?> </label> </div> </div> @@ -310,9 +273,7 @@ <fieldset class="admin__fieldset bulk-attribute-values"> <div class="admin__field _required"> <label class="admin__field-label" for="apply-images-attributes"> - <span><?= $block->escapeHtml( - __('Select attribute') - ); ?></span> + <span><?= $block->escapeHtml(__('Select attribute')); ?></span> </label> <div class="admin__field-control"> <select @@ -322,9 +283,7 @@ options: $parent.attributes, optionsText: 'label', value: attribute, - optionsCaption: '<?= $block->escapeHtml( - __("Select") - ); ?>' + optionsCaption: '<?= $block->escapeHtml(__("Select")); ?>' "> </select> </div> @@ -341,24 +300,17 @@ <div data-role="gallery" class="gallery" data-images="[]" - data-types="<?= $block->escapeHtml( - $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes()) - ) ?>" - > + data-types="<?= $block->escapeHtml($this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getImageTypes())) ?>"> <div class="image image-placeholder"> <div data-role="uploader" class="uploader"> <div class="image-browse"> - <span><?= $block->escapeHtml( - __('Browse Files...') - ); ?></span> + <span><?= $block->escapeHtml(__('Browse Files...')); ?></span> <input type="file" name="image" multiple="multiple" data-url="<?= /* @noEscape */ $block->getUrl('catalog/product_gallery/upload') ?>" /> </div> </div> <div class="product-image-wrapper"> - <p class="image-placeholder-text"><?= $block->escapeHtml( - __('Browse to find or drag image here') - ); ?></p> + <p class="image-placeholder-text"><?= $block->escapeHtml(__('Browse to find or drag image here')); ?></p> </div> <div class="spinner"> <span></span><span></span><span></span><span></span> @@ -366,15 +318,12 @@ </div> </div> - <?php foreach ($block->getImageTypes() as $typeData): - ?> + <?php foreach ($block->getImageTypes() as $typeData) :?> <input name="<?= $block->escapeHtml($typeData['name']) ?>" class="image-<?= $block->escapeHtml($typeData['code']) ?>" type="hidden" value="<?= $block->escapeHtml($typeData['value']) ?>"/> - <?php - endforeach; - ?> + <?php endforeach; ?> <script data-template="uploader" type="text/x-magento-template"> <div id="<%- data.id %>" class="file-row"> @@ -418,15 +367,11 @@ class="action-remove" data-role="delete-button" title="<?= $block->escapeHtml(__('Remove image')) ?>"> - <span><?= $block->escapeHtml( - __('Remove image') - ); ?></span> + <span><?= $block->escapeHtml(__('Remove image')); ?></span> </button> <div class="draggable-handle"></div> </div> - <div class="image-fade"><span><?= $block->escapeHtml( - __('Hidden') - ); ?></span></div> + <div class="image-fade"><span><?= $block->escapeHtml(__('Hidden')); ?></span></div> </div> <div class="item-description"> <div class="item-title" data-role="img-title"><%- data.label %></div> @@ -435,19 +380,12 @@ </div> </div> <ul class="item-roles" data-role="roles-labels"> - <?php - foreach ($block->getMediaAttributes() as $attribute): - ?> - <li data-role-code="<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>" class="item-role item-role-<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>"> + <?php foreach ($block->getMediaAttributes() as $attribute) :?> + <li data-role-code="<?= $block->escapeHtml($attribute->getAttributeCode()) ?>" + class="item-role item-role-<?= $block->escapeHtml($attribute->getAttributeCode()) ?>"> <?= $block->escapeHtml($attribute->getFrontendLabel()) ?> </li> - <?php - endforeach; - ?> + <?php endforeach; ?> </ul> </div> </script> @@ -492,9 +430,7 @@ <fieldset class="admin__fieldset fieldset-image-panel"> <div class="admin__field field-image-description"> <label class="admin__field-label" for="image-description"> - <span><?= $block->escapeHtml( - __('Alt Text') - );?></span> + <span><?= $block->escapeHtml(__('Alt Text'));?></span> </label> <div class="admin__field-control"> @@ -508,56 +444,38 @@ <div class="admin__field field-image-role"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Role') - );?></span> + <span><?= $block->escapeHtml(__('Role'));?></span> </label> <div class="admin__field-control"> <ul class="multiselect-alt"> - <?php - foreach ($block->getMediaAttributes() as $attribute) : - ?> + <?php foreach ($block->getMediaAttributes() as $attribute) :?> <li class="item"> <label> <input class="image-type" data-role="type-selector" type="checkbox" - value="<?= $block->escapeHtml( - $attribute->getAttributeCode() - ) ?>" + value="<?= $block->escapeHtml($attribute->getAttributeCode()) ?>" /> - <?= $block->escapeHtml( - $attribute->getFrontendLabel() - ) ?> + <?= $block->escapeHtml($attribute->getFrontendLabel()) ?> </label> </li> - <?php - endforeach; - ?> + <?php endforeach; ?> </ul> </div> </div> <div class="admin__field admin__field-inline field-image-size" data-role="size"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Image Size') - ); ?></span> + <span><?= $block->escapeHtml(__('Image Size')); ?></span> </label> - <div class="admin__field-value" data-message="<?= $block->escapeHtml( - __('{size}') - ); ?>"></div> + <div class="admin__field-value" data-message="<?= $block->escapeHtml(__('{size}')); ?>"></div> </div> <div class="admin__field admin__field-inline field-image-resolution" data-role="resolution"> <label class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Image Resolution') - ); ?></span> + <span><?= $block->escapeHtml(__('Image Resolution')); ?></span> </label> - <div class="admin__field-value" data-message="<?= $block->escapeHtml( - __('{width}^{height} px') - ); ?>"></div> + <div class="admin__field-value" data-message="<?= $block->escapeHtml(__('{width}^{height} px')); ?>"></div> </div> <div class="admin__field field-image-hide"> @@ -572,9 +490,7 @@ <% if (data.disabled == 1) { %>checked="checked"<% } %> /> <label for="hide-from-product-page" class="admin__field-label"> - <?= $block->escapeHtml( - __('Hide from Product Page') - ); ?> + <?= $block->escapeHtml(__('Hide from Product Page')); ?> </label> </div> </div> @@ -593,9 +509,7 @@ <div data-bind="with: sections().price" class="steps-wizard-section"> <div data-role="section"> <div class="steps-wizard-section-title"> - <span><?= $block->escapeHtml( - __('Price') - ); ?></span> + <span><?= $block->escapeHtml(__('Price')); ?></span> </div> <ul class="steps-wizard-section-list"> <li> @@ -607,9 +521,7 @@ data-bind="checked:type" /> <label for="apply-single-price-radio" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Apply single price to all SKUs') - ); ?></span> + <span><?= $block->escapeHtml(__('Apply single price to all SKUs')); ?></span> </label> </div> </li> @@ -622,9 +534,7 @@ data-bind="checked:type" /> <label for="apply-unique-prices-radio" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Apply unique prices by attribute to each SKU') - ); ?></span> + <span><?= $block->escapeHtml(__('Apply unique prices by attribute to each SKU')); ?></span> </label> </div> </li> @@ -637,9 +547,7 @@ checked data-bind="checked:type" /> <label for="skip-pricing-radio" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Skip price at this time') - ); ?></span> + <span><?= $block->escapeHtml(__('Skip price at this time')); ?></span> </label> </div> </li> @@ -648,9 +556,7 @@ <fieldset class="admin__fieldset bulk-attribute-values" data-bind="visible: type() == 'single'"> <div class="admin__field _required"> <label for="apply-single-price-input" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Price') - ); ?></span> + <span><?= $block->escapeHtml(__('Price')); ?></span> </label> <div class="admin__field-control"> <div class="currency-addon"> @@ -667,9 +573,7 @@ <fieldset class="admin__fieldset bulk-attribute-values"> <div class="admin__field _required"> <label for="select-each-price" class="admin__field-label"> - <span><?= $block->escapeHtml( - __('Select attribute') - ); ?></span> + <span><?= $block->escapeHtml(__('Select attribute')); ?></span> </label> <div class="admin__field-control"> <select id="select-each-price" class="admin__control-select" data-bind=" diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml index cfb742e80f719..c3dc614232201 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/select_attributes.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\SelectAttributes */ ?> <div class="select-attributes-block <?= /* @noEscape */ $block->getData('config/dataScope') ?>" data-role="select-attributes-step"> @@ -13,8 +11,8 @@ <?= /* @noEscape */ $block->getAddNewAttributeButton() ?> </div> <h2 class="steps-wizard-title"><?= $block->escapeHtml( - __('Step 1: Select Attributes') - ); ?></h2> + __('Step 1: Select Attributes') + ); ?></h2> <div class="selected-attributes" data-bind="scope: '<?= /* @noEscape */ $block->getComponentName() ?>'"> <?= $block->escapeHtml( __('Selected Attributes:') diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml index 2ded3aa1079a9..379e129b68c7e 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/summary.phtml @@ -4,14 +4,12 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Summary */ ?> <div data-bind="scope: '<?= /* @noEscape */ $block->getComponentName() ?>'"> <h2 class="steps-wizard-title"><?= $block->escapeHtml( - __('Step 4: Summary') - ); ?></h2> + __('Step 4: Summary') + ); ?></h2> <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> <!-- ko if: gridNew().length --> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml index 07f4e39e43de6..c11a1adc19896 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml @@ -4,34 +4,32 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Variations\Config */ +/** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Variations\Config */ ?> -<div class="entry-edit form-inline" id="<?= /* @escapeNotVerified */ $block->getId() ?>" data-panel="product-variations"> +<div class="entry-edit form-inline" id="<?= $block->escapeHtmlAttr($block->getId()) ?>" data-panel="product-variations"> <div data-bind="scope: 'variation-steps-wizard'" class="product-create-configuration"> <div class="product-create-configuration-info"> <div class="note" data-role="product-create-configuration-info"> - <?= /* @escapeNotVerified */ __('Configurable products allow customers to choose options (Ex: shirt color). - You need to create a simple product for each configuration (Ex: a product for each color).');?> + <?= $block->escapeHtml(__('Configurable products allow customers to choose options (Ex: shirt color). + You need to create a simple product for each configuration (Ex: a product for each color).'));?> </div> </div> <div class="product-create-configuration-actions" data-action="product-create-configuration-buttons"> <div class="product-create-configuration-action"> <button type="button" data-action="open-steps-wizard" title="Create Product Configurations" class="action-secondary" data-bind="click: open"> - <span data-role="button-label" data-edit-label="<?= /* @escapeNotVerified */ __('Edit Configurations') ?>"> - <?= /* @escapeNotVerified */ $block->isHasVariations() + <span data-role="button-label" data-edit-label="<?= $block->escapeHtmlAttr(__('Edit Configurations')) ?>"> + <?= $block->escapeHtml($block->isHasVariations() ? __('Edit Configurations') - : __('Create Configurations') - ?> + : __('Create Configurations')) +?> </span> </button> </div> <div class="product-create-configuration-action" data-bind="scope: 'configurableProductGrid'"> <button class="action-tertiary action-menu-item" type="button" data-action="choose" data-bind="click: showManuallyGrid, visible: button"> - <?= /* @noEscape */ __('Add Products Manually') ?> + <?= $block->escapeHtml(__('Add Products Manually')) ?> </button> </div> </div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml index 230e0fd14ccb6..22ff1992c94a7 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Variations\Config\Matrix */ ?> <?php @@ -17,7 +15,7 @@ $currencySymbol = $block->getCurrencySymbol(); <div id="product-variations-matrix" data-role="product-variations-matrix"> <div data-bind="scope: 'configurableVariations'"> <h3 class="hidden" data-bind="css: {hidden: !showVariations() }" class="title"> - <?= /* @escapeNotVerified */ __('Current Variations') ?> + <?= $block->escapeHtml(__('Current Variations')) ?> </h3> <script data-template-for="variation-image" type="text/x-magento-template"> @@ -47,22 +45,22 @@ $currencySymbol = $block->getCurrencySymbol(); <thead> <tr> <th class="data-grid-th data-grid-thumbnail-cell col-image" data-column="image"> - <?= /* @escapeNotVerified */ __('Image') ?> + <?= $block->escapeHtml(__('Image')) ?> </th> <th class="data-grid-th col-name" data-column="name"> - <?= /* @escapeNotVerified */ __('Name') ?> + <?= $block->escapeHtml(__('Name')) ?> </th> <th class="data-grid-th col-sku" data-column="sku"> - <?= /* @escapeNotVerified */ __('SKU') ?> + <?= $block->escapeHtml(__('SKU')) ?> </th> <th class="data-grid-th col-price" data-column="price"> - <?= /* @escapeNotVerified */ __('Price') ?> + <?= $block->escapeHtml(__('Price')) ?> </th> <th class="data-grid-th col-qty" data-column="qty"> - <?= /* @escapeNotVerified */ __('Quantity') ?> + <?= $block->escapeHtml(__('Quantity')) ?> </th> <th class="data-grid-th col-weight" data-column="weight"> - <?= /* @escapeNotVerified */ __('Weight') ?> + <?= $block->escapeHtml(__('Weight')) ?> </th> <!-- ko foreach: getAttributesOptions() --> <th data-bind="attr: {class:'data-grid-th col-' + $data.attribute_code}, @@ -70,7 +68,7 @@ $currencySymbol = $block->getCurrencySymbol(); </th> <!-- /ko --> <th class="data-grid-th"> - <?= /* @escapeNotVerified */ __('Actions') ?> + <?= $block->escapeHtml(__('Actions')) ?> </th> </tr> </thead> @@ -88,7 +86,7 @@ $currencySymbol = $block->getCurrencySymbol(); <input type="hidden" data-bind=" attr: {id: $parent.getRowId(variation, 'image'), name: $parent.getVariationRowName(variation, 'image')}"/> - <span><?= /* @escapeNotVerified */ __('Upload Image') ?></span> + <span><?= $block->escapeHtml(__('Upload Image')) ?></span> <input name="image" type="file" data-url="<?= $block->escapeHtml($block->getImageUploadUrl()) ?>" title="<?= $block->escapeHtml(__('Upload image')) ?>"/> @@ -102,11 +100,11 @@ $currencySymbol = $block->getCurrencySymbol(); <!-- /ko --> <button type="button" class="action toggle no-display" data-toggle="dropdown" data-mage-init='{"dropdown":{}}'> - <span><?= /* @escapeNotVerified */ __('Select') ?></span> + <span><?= $block->escapeHtml(__('Select')) ?></span> </button> <ul class="dropdown"> <li> - <a class="item" data-action="no-image"><?= /* @escapeNotVerified */ __('No Image') ?></a> + <a class="item" data-action="no-image"><?= $block->escapeHtml(__('No Image')) ?></a> </li> </ul> </div> @@ -208,7 +206,7 @@ $currencySymbol = $block->getCurrencySymbol(); " data-action="choose" href="#"> - <?= /* @escapeNotVerified */ __('Choose a different Product') ?> + <?= $block->escapeHtml(__('Choose a different Product')) ?> </a> </li> <li> @@ -219,7 +217,7 @@ $currencySymbol = $block->getCurrencySymbol(); </li> <li> <a class="action-menu-item" data-bind="click: $parent.removeProduct.bind($parent, $index())"> - <?= /* @escapeNotVerified */ __('Remove Product') ?> + <?= $block->escapeHtml(__('Remove Product')) ?> </a> </li> </ul> @@ -233,15 +231,14 @@ $currencySymbol = $block->getCurrencySymbol(); <!-- /ko --> </div> <div data-role="step-wizard-dialog" - data-mage-init='{"Magento_Ui/js/modal/modal":{"type":"slide","title":"<?= /* @escapeNotVerified */ __('Create Product Configurations') ?>", + data-mage-init='{"Magento_Ui/js/modal/modal":{"type":"slide","title":"<?= $block->escapeJs(__('Create Product Configurations')) ?>", "buttons":[]}}' class="no-display"> - <?php - /* @escapeNotVerified */ echo $block->getVariationWizard([ + <?= /* @noEscape */ $block->getVariationWizard([ 'attributes' => $attributes, 'configurations' => $productMatrix ]); - ?> +?> </div> </div> @@ -252,8 +249,8 @@ $currencySymbol = $block->getCurrencySymbol(); "components": { "configurableVariations": { "component": "Magento_ConfigurableProduct/js/variations/variations", - "variations": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($productMatrix) ?>, - "productAttributes": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($attributes) ?>, + "variations": <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($productMatrix) ?>, + "productAttributes": <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($attributes) ?>, "productUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product/edit', ['id' => '%id%']) ?>", "currencySymbol": "<?= /* @noEscape */ $currencySymbol ?>", "configurableProductGrid": "configurableProductGrid" diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml index 2e38633218652..7b85efdbb73aa 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard-ajax.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Variations\Config\Matrix */ $productMatrix = $block->getProductMatrix(); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml index 3a23257cbbf9b..f009962bb97ff 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/wizard.phtml @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Variations\Config\Matrix */ ?> <?php @@ -48,9 +46,9 @@ $currencySymbol = $block->getCurrencySymbol(); "attributeSetHandler": "<?= /* @noEscape */ $block->getForm() ?>.configurable_attribute_set_handler_modal", "wizardModalButtonName": "<?= /* @noEscape */ $block->getForm() ?>.configurable.configurable_products_button_set.create_configurable_products_button", "wizardModalButtonTitle": "<?= $block->escapeHtml(__('Edit Configurations')) ?>", - "productAttributes": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($attributes) ?>, + "productAttributes": <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($attributes) ?>, "productUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product/edit', ['id' => '%id%']) ?>", - "variations": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($productMatrix) ?>, + "variations": <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($productMatrix) ?>, "currencySymbol": "<?= /* @noEscape */ $currencySymbol ?>", "attributeSetCreationUrl": "<?= /* @noEscape */ $block->getUrl('*/product_set/save') ?>" } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml index 6c993b243da23..6b30b3eba33b4 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\Framework\View\Element\Template */ ?> <div data-role="affected-attribute-set-selector" class="no-display affected-attribute-set"> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml index 0e0eb3464eaa6..cdb12b54e5e67 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var $block \Magento\ConfigurableProduct\Block\Product\Configurable\AttributeSelector */ ?> <script> @@ -48,12 +46,12 @@ $form .modal({ - title: '<?= /* @escapeNotVerified */ __('Choose Affected Attribute Set') ?>', + title: '<?= $block->escapeJs(__('Choose Affected Attribute Set')) ?>', closed: function () { resetValidation(); }, buttons: [{ - text: '<?= /* @escapeNotVerified */ __('Confirm') ?>', + text: '<?= $block->escapeJs(__('Confirm')) ?>', attr: { 'data-action': 'confirm' }, @@ -77,12 +75,12 @@ $.ajax({ type: 'POST', - url: '<?= /* @escapeNotVerified */ $block->getAttributeSetCreationUrl() ?>', + url: '<?= $block->escapeUrl($block->getAttributeSetCreationUrl()) ?>', data: { gotoEdit: 1, attribute_set_name: $form.find('input[name=new-attribute-set-name]').val(), skeleton_set: $('#attribute_set_id').val(), - form_key: '<?= /* @escapeNotVerified */ $block->getFormKey() ?>', + form_key: '<?= $block->escapeJs($block->getFormKey()) ?>', return_session_messages_only: 1 }, dataType: 'json', @@ -101,8 +99,8 @@ return false; } },{ - text: '<?= /* @escapeNotVerified */ __('Cancel') ?>', - id: '<?= /* @escapeNotVerified */ $block->getJsId('close-button') ?>', + text: '<?= $block->escapeJs(__('Cancel')) ?>', + id: '<?= $block->escapeJs($block->getJsId('close-button')) ?>', 'class': 'action-close', click: function() { $form.modal('closeModal'); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/attribute-selector/js.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/attribute-selector/js.phtml index 4246d8f53a79c..e6cf1e9c6870d 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/attribute-selector/js.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/attribute-selector/js.phtml @@ -3,16 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - +// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis /** @var $block \Magento\ConfigurableProduct\Block\Product\Configurable\AttributeSelector */ ?> <script> require(["jquery","mage/mage","mage/backend/suggest"], function($){ - var options = <?php - /* @escapeNotVerified */ echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getSuggestWidgetOptions()) - ?>; + var options = <?= /* @noEscape */ $this->helper(Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getSuggestWidgetOptions()) ?>; $('#configurable-attribute-selector') .mage('suggest', options) .on('suggestselect', function (event, ui) { diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js index 61dfe04a90f7e..4aaa8aaffd2b6 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/modal-configurable.js @@ -34,6 +34,8 @@ define([ } this._super(); + } else { + this.form().focusInvalid(); } } }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/paging/sizes.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/paging/sizes.js index a8b8f95f6536f..155b176bd431b 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/paging/sizes.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/paging/sizes.js @@ -9,21 +9,21 @@ define([ return Sizes.extend({ defaults: { - excludedOptions: ['100', '200'] - }, - - /** - * @override - */ - initialize: function () { - this._super(); - - this.excludedOptions.forEach(function (excludedOption) { - delete this.options[excludedOption]; - }, this); - this.updateArray(); - - return this; + options: { + '20': { + value: 20, + label: 20 + }, + '30': { + value: 30, + label: 30 + }, + '50': { + value: 50, + label: 50 + } + }, + value: 20 } }); }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js index 1fa65e03f54e0..6c790c634ee93 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js @@ -90,13 +90,46 @@ define([ * @param {Object} option */ saveOption: function (option) { - if (!_.isEmpty(option.label)) { + if (this.isValidOption(option)) { this.options.remove(option); this.options.push(option); this.chosenOptions.push(option.id); } }, + /** + * @param {Object} newOption + * @return boolean + */ + isValidOption: function (newOption) { + var duplicatedOptions = [], + errorOption, + allOptions = []; + + if (_.isEmpty(newOption.label)) { + return false; + } + + _.each(this.options(), function (option) { + if (!_.isUndefined(allOptions[option.label]) && newOption.label === option.label) { + duplicatedOptions.push(option); + } + + allOptions[option.label] = option.label; + }); + + if (duplicatedOptions.length) { + _.each(duplicatedOptions, function (duplicatedOption) { + errorOption = $('[data-role="' + duplicatedOption.id + '"]'); + errorOption.addClass('_error'); + }); + + return false; + } + + return true; + }, + /** * @param {Object} option */ @@ -128,6 +161,7 @@ define([ })); attribute.opened = ko.observable(this.initialOpened(index)); attribute.collapsible = ko.observable(true); + attribute.isValidOption = this.isValidOption; return attribute; }, @@ -148,22 +182,22 @@ define([ saveAttribute: function () { var errorMessage = $.mage.__('Select options for all attributes or remove unused attributes.'); - this.attributes.each(function (attribute) { + if (!this.attributes().length) { + throw new Error(errorMessage); + } + + _.each(this.attributes(), function (attribute) { attribute.chosen = []; if (!attribute.chosenOptions.getLength()) { throw new Error(errorMessage); } - attribute.chosenOptions.each(function (id) { + _.each(attribute.chosenOptions(), function (id) { attribute.chosen.push(attribute.options.findWhere({ id: id })); }); }); - - if (!this.attributes().length) { - throw new Error(errorMessage); - } }, /** @@ -184,33 +218,47 @@ define([ * @return {Boolean} */ saveOptions: function () { - var options = []; + var newOptions = []; - this.attributes.each(function (attribute) { - attribute.chosenOptions.each(function (id) { + _.each(this.attributes(), function (attribute) { + _.each(attribute.options(), function (element) { var option = attribute.options.findWhere({ - id: id, - 'is_new': true + id: element.id }); - if (option) { - options.push(option); + if (option['is_new'] === true) { + if (!attribute.isValidOption(option)) { + throw new Error( + $.mage.__('The value of attribute ""%1"" must be unique') + .replace('"%1"', attribute.label) + ); + } + + newOptions.push(option); } }); }); - if (!options.length) { + if (!newOptions.length) { return false; } + $.ajax({ type: 'POST', url: this.createOptionsUrl, data: { - options: options + options: newOptions }, showLoader: true }).done(function (savedOptions) { - this.attributes.each(function (attribute) { + if (savedOptions.error) { + this.notificationMessage.error = savedOptions.error; + this.notificationMessage.text = savedOptions.message; + + return; + } + + _.each(this.attributes(), function (attribute) { _.each(savedOptions, function (newOptionId, oldOptionId) { var option = attribute.options.findWhere({ id: oldOptionId diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml index f020e4c2c9495..a6dc6c819989e 100644 --- a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml @@ -4,37 +4,28 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - -?> - -<?php /** @var \Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox$block */ - /** @var \Magento\Framework\Pricing\Price\PriceInterface $priceModel */ $priceModel = $block->getPriceType('regular_price'); - /** @var \Magento\Framework\Pricing\Price\PriceInterface $finalPriceModel */ $finalPriceModel = $block->getPriceType('final_price'); $idSuffix = $block->getIdSuffix() ? $block->getIdSuffix() : ''; $schema = ($block->getZone() == 'item_view') ? true : false; ?> <span class="normal-price"> - <?php - $arguments = [ + <?= /* @noEscape */ $block->renderAmount($finalPriceModel->getAmount(), [ 'display_label' => __('As low as'), 'price_id' => $block->getPriceId('product-price-' . $idSuffix), 'price_type' => 'finalPrice', 'include_container' => true, 'schema' => $schema, - ]; - /* @noEscape */ echo $block->renderAmount($finalPriceModel->getAmount(), $arguments); - ?> + ]); +?> </span> -<?php if (!$block->isProductList() && $block->hasSpecialPrice()): ?> +<?php if (!$block->isProductList() && $block->hasSpecialPrice()) : ?> <span class="old-price sly-old-price no-display"> - <?php /* @escapeNotVerified */ echo $block->renderAmount($priceModel->getAmount(), [ + <?= /* @noEscape */ $block->renderAmount($priceModel->getAmount(), [ 'display_label' => __('Regular Price'), 'price_id' => $block->getPriceId('old-price-' . $idSuffix), 'price_type' => 'oldPrice', @@ -44,14 +35,14 @@ $schema = ($block->getZone() == 'item_view') ? true : false; </span> <?php endif; ?> -<?php if ($block->showMinimalPrice()): ?> - <?php if ($block->getUseLinkForAsLowAs()):?> - <a href="<?= /* @escapeNotVerified */ $block->getSaleableItem()->getProductUrl() ?>" class="minimal-price-link"> - <?= /* @escapeNotVerified */ $block->renderAmountMinimal() ?> +<?php if ($block->showMinimalPrice()) : ?> + <?php if ($block->getUseLinkForAsLowAs()) :?> + <a href="<?= $block->escapeUrl($block->getSaleableItem()->getProductUrl()) ?>" class="minimal-price-link"> + <?= /* @noEscape */ $block->renderAmountMinimal() ?> </a> - <?php else:?> + <?php else :?> <span class="minimal-price-link"> - <?= /* @escapeNotVerified */ $block->renderAmountMinimal() ?> + <?= /* @noEscape */ $block->renderAmountMinimal() ?> </span> <?php endif?> <?php endif; ?> diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml index 325ee1d5d79b3..c68419b955e6d 100644 --- a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml +++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - ?> <script type="text/x-magento-template" id="tier-prices-template"> <ul class="prices-tier items"> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/templates/js/components.phtml b/app/code/Magento/ConfigurableProduct/view/frontend/templates/js/components.phtml index bad5acc209b5f..5902a9f25cc4b 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/templates/js/components.phtml +++ b/app/code/Magento/ConfigurableProduct/view/frontend/templates/js/components.phtml @@ -3,8 +3,5 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?= $block->getChildHtml() ?> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml b/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml index f5ed067967547..f7db41225c970 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml +++ b/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php @@ -13,19 +10,19 @@ $_product = $block->getProduct(); $_attributes = $block->decorateArray($block->getAllowAttributes()); ?> -<?php if ($_product->isSaleable() && count($_attributes)):?> - <?php foreach ($_attributes as $_attribute): ?> +<?php if ($_product->isSaleable() && count($_attributes)) :?> + <?php foreach ($_attributes as $_attribute) : ?> <div class="field configurable required"> - <label class="label" for="attribute<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>"> + <label class="label" for="attribute<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>"> <span><?= $block->escapeHtml($_attribute->getProductAttribute()->getStoreLabel()) ?></span> </label> <div class="control"> - <select name="super_attribute[<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>]" - data-selector="super_attribute[<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>]" + <select name="super_attribute[<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>]" + data-selector="super_attribute[<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>]" data-validate="{required:true}" - id="attribute<?= /* @escapeNotVerified */ $_attribute->getAttributeId() ?>" + id="attribute<?= $block->escapeHtmlAttr($_attribute->getAttributeId()) ?>" class="super-attribute-select"> - <option value=""><?= /* @escapeNotVerified */ __('Choose an Option...') ?></option> + <option value=""><?= $block->escapeHtml(__('Choose an Option...')) ?></option> </select> </div> </div> @@ -34,9 +31,11 @@ $_attributes = $block->decorateArray($block->getAllowAttributes()); { "#product_addtocart_form": { "configurable": { - "spConfig": <?= /* @escapeNotVerified */ $block->getJsonConfig() ?>, - "gallerySwitchStrategy": "<?php /* @escapeNotVerified */ echo $block->getVar('gallery_switch_strategy', - 'Magento_ConfigurableProduct') ?: 'replace'; ?>" + "spConfig": <?= /* @noEscape */ $block->getJsonConfig() ?>, + "gallerySwitchStrategy": "<?= $block->escapeJs($block->getVar( + 'gallery_switch_strategy', + 'Magento_ConfigurableProduct' + ) ?: 'replace'); ?>" } }, "*" : { diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index ef40dcb9a7323..c0128dffe7045 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -12,7 +12,7 @@ define([ 'mage/translate', 'priceUtils', 'priceBox', - 'jquery/ui', + 'jquery-ui-modules/widget', 'jquery/jquery.parsequery' ], function ($, _, mageTemplate, $t, priceUtils) { 'use strict'; diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php new file mode 100644 index 0000000000000..f1971e228ac05 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Cart/BuyRequest/SuperAttributeDataProvider.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\Stdlib\ArrayManager; +use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestDataProviderInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\ConfigurableProductGraphQl\Model\Options\Collection as OptionCollection; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * DataProvider for building super attribute options in buy requests + */ +class SuperAttributeDataProvider implements BuyRequestDataProviderInterface +{ + /** + * @var ArrayManager + */ + private $arrayManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var OptionCollection + */ + private $optionCollection; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @param ArrayManager $arrayManager + * @param ProductRepositoryInterface $productRepository + * @param OptionCollection $optionCollection + * @param MetadataPool $metadataPool + */ + public function __construct( + ArrayManager $arrayManager, + ProductRepositoryInterface $productRepository, + OptionCollection $optionCollection, + MetadataPool $metadataPool + ) { + $this->arrayManager = $arrayManager; + $this->productRepository = $productRepository; + $this->optionCollection = $optionCollection; + $this->metadataPool = $metadataPool; + } + + /** + * @inheritdoc + */ + public function execute(array $cartItemData): array + { + $parentSku = $this->arrayManager->get('parent_sku', $cartItemData); + if ($parentSku === null) { + return []; + } + $sku = $this->arrayManager->get('data/sku', $cartItemData); + + try { + $parentProduct = $this->productRepository->get($parentSku); + $product = $this->productRepository->get($sku); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__('Could not find specified product.')); + } + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $this->optionCollection->addProductId((int)$parentProduct->getData($linkField)); + $options = $this->optionCollection->getAttributesByProductId((int)$parentProduct->getData($linkField)); + + $superAttributesData = []; + foreach ($options as $option) { + $code = $option['attribute_code']; + foreach ($option['values'] as $optionValue) { + if ($optionValue['value_index'] === $product->getData($code)) { + $superAttributesData[$option['attribute_id']] = $optionValue['value_index']; + break; + } + } + } + return ['super_attribute' => $superAttributesData]; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/AddConfigurableProductsToCart.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/AddConfigurableProductsToCart.php new file mode 100644 index 0000000000000..cd7ee35248473 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/AddConfigurableProductsToCart.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\QuoteGraphQl\Model\Cart\AddProductsToCart; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; + +/** + * Add configurable products to cart GraphQl resolver + * {@inheritdoc} + */ +class AddConfigurableProductsToCart implements ResolverInterface +{ + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @var AddProductsToCart + */ + private $addProductsToCart; + + /** + * @param GetCartForUser $getCartForUser + * @param AddProductsToCart $addProductsToCart + */ + public function __construct( + GetCartForUser $getCartForUser, + AddProductsToCart $addProductsToCart + ) { + $this->getCartForUser = $getCartForUser; + $this->addProductsToCart = $addProductsToCart; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + if (!isset($args['input']['cart_items']) || empty($args['input']['cart_items']) + || !is_array($args['input']['cart_items']) + ) { + throw new GraphQlInputException(__('Required parameter "cart_items" is missing')); + } + $cartItems = $args['input']['cart_items']; + + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId); + $this->addProductsToCart->execute($cart, $cartItems); + + return [ + 'cart' => [ + 'model' => $cart, + ], + ]; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableCartItemOptions.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableCartItemOptions.php new file mode 100644 index 0000000000000..fed05208e0d55 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableCartItemOptions.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Resolver; + +use Magento\Catalog\Helper\Product\Configuration; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Quote\Model\Quote\Item; + +/** + * @inheritdoc + */ +class ConfigurableCartItemOptions implements ResolverInterface +{ + /** + * @var Configuration + */ + private $configurationHelper; + + /** + * @param Configuration $configurationHelper + */ + public function __construct( + Configuration $configurationHelper + ) { + $this->configurationHelper = $configurationHelper; + } + + /** + * Fetch and format configurable variants. + * + * @param Field $field + * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array|\Magento\Framework\GraphQl\Query\Resolver\Value|mixed + * @throws LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + /** @var Item $cartItem */ + $cartItem = $value['model']; + + $result = []; + foreach ($this->configurationHelper->getOptions($cartItem) as $option) { + $result[] = [ + 'id' => $option['option_id'], + 'option_label' => $option['label'], + 'value_id' => $option['option_value'], + 'value_label' => $option['value'], + ]; + } + + return $result; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php index 12571602878d1..d517c9aa29bd3 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php @@ -9,12 +9,11 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\ProductFactory; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product as DataProvider; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface; /** * Collection for fetching configurable child product data. @@ -26,21 +25,11 @@ class Collection */ private $childCollectionFactory; - /** - * @var ProductFactory - */ - private $productFactory; - /** * @var SearchCriteriaBuilder */ private $searchCriteriaBuilder; - /** - * @var DataProvider - */ - private $productDataProvider; - /** * @var MetadataPool */ @@ -61,25 +50,27 @@ class Collection */ private $attributeCodes = []; + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + /** * @param CollectionFactory $childCollectionFactory - * @param ProductFactory $productFactory * @param SearchCriteriaBuilder $searchCriteriaBuilder - * @param DataProvider $productDataProvider * @param MetadataPool $metadataPool + * @param CollectionProcessorInterface $collectionProcessor */ public function __construct( CollectionFactory $childCollectionFactory, - ProductFactory $productFactory, SearchCriteriaBuilder $searchCriteriaBuilder, - DataProvider $productDataProvider, - MetadataPool $metadataPool + MetadataPool $metadataPool, + CollectionProcessorInterface $collectionProcessor ) { $this->childCollectionFactory = $childCollectionFactory; - $this->productFactory = $productFactory; $this->searchCriteriaBuilder = $searchCriteriaBuilder; - $this->productDataProvider = $productDataProvider; $this->metadataPool = $metadataPool; + $this->collectionProcessor = $collectionProcessor; } /** @@ -148,7 +139,11 @@ private function fetch() : array /** @var ChildCollection $childCollection */ $childCollection = $this->childCollectionFactory->create(); $childCollection->setProductFilter($product); - $childCollection->addAttributeToSelect($attributeData); + $this->collectionProcessor->process( + $childCollection, + $this->searchCriteriaBuilder->create(), + $attributeData + ); /** @var Product $childProduct */ foreach ($childCollection->getItems() as $childProduct) { diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index 8d91356d30f31..fe14f329361a8 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -3,10 +3,12 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-catalog": "103.0.*", "magento/module-configurable-product": "100.3.*", "magento/module-catalog-graph-ql": "100.3.*", + "magento/module-quote": "101.1.*", + "magento/module-quote-graph-ql": "100.3.*", "magento/framework": "102.0.*" }, "license": [ @@ -21,5 +23,5 @@ "Magento\\ConfigurableProductGraphQl\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml index f9d3c8b769795..1d72524a31b76 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml @@ -22,4 +22,11 @@ </argument> </arguments> </type> + <type name="Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder"> + <arguments> + <argument name="providers" xsi:type="array"> + <item name="super_attribute" xsi:type="object">Magento\ConfigurableProductGraphQl\Model\Cart\BuyRequest\SuperAttributeDataProvider</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls index 91f7ee5eb3209..5053ed848b4e5 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls @@ -1,7 +1,7 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. type Mutation { - addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") + addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\AddConfigurableProductsToCart") } type ConfigurableProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "ConfigurableProduct defines basic features of a configurable product and its simple product variants") { @@ -50,13 +50,14 @@ type AddConfigurableProductsToCartOutput { input ConfigurableProductCartItemInput { data: CartItemInput! - variant_sku: String! + variant_sku: String @deprecated(reason: "Use CartItemInput.sku instead.") + parent_sku: String @doc(description: "Configurable product SKU.") customizable_options:[CustomizableOptionInput!] } type ConfigurableCartItem implements CartItemInterface { customizable_options: [SelectedCustomizableOption]! - configurable_options: [SelectedConfigurableOption!]! + configurable_options: [SelectedConfigurableOption!]! @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\ConfigurableCartItemOptions") } type SelectedConfigurableOption { diff --git a/app/code/Magento/ConfigurableProductSales/composer.json b/app/code/Magento/ConfigurableProductSales/composer.json index 0509c294bb8be..87a5c9e7b6b2e 100644 --- a/app/code/Magento/ConfigurableProductSales/composer.json +++ b/app/code/Magento/ConfigurableProductSales/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-catalog": "103.0.*", "magento/module-sales": "102.0.*", @@ -27,5 +27,5 @@ "Magento\\ConfigurableProductSales\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index d1cba9838664e..60cb1fe64efb2 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-cms": "103.0.*", "magento/module-config": "101.1.*", @@ -25,5 +25,5 @@ "Magento\\Contact\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Contact/view/frontend/templates/form.phtml b/app/code/Magento/Contact/view/frontend/templates/form.phtml index d64a991bcafad..673bdc73840dc 100644 --- a/app/code/Magento/Contact/view/frontend/templates/form.phtml +++ b/app/code/Magento/Contact/view/frontend/templates/form.phtml @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile /** @var \Magento\Contact\Block\ContactForm $block */ ?> <form class="form contact" @@ -19,25 +18,25 @@ <div class="field name required"> <label class="label" for="name"><span><?= $block->escapeHtml(__('Name')) ?></span></label> <div class="control"> - <input name="name" id="name" title="<?= $block->escapeHtmlAttr(__('Name')) ?>" value="<?= $block->escapeHtmlAttr($this->helper('Magento\Contact\Helper\Data')->getPostValue('name') ?: $this->helper('Magento\Contact\Helper\Data')->getUserName()) ?>" class="input-text" type="text" data-validate="{required:true}"/> + <input name="name" id="name" title="<?= $block->escapeHtmlAttr(__('Name')) ?>" value="<?= $block->escapeHtmlAttr($this->helper(\Magento\Contact\Helper\Data::class)->getPostValue('name') ?: $this->helper(\Magento\Contact\Helper\Data::class)->getUserName()) ?>" class="input-text" type="text" data-validate="{required:true}"/> </div> </div> <div class="field email required"> <label class="label" for="email"><span><?= $block->escapeHtml(__('Email')) ?></span></label> <div class="control"> - <input name="email" id="email" title="<?= $block->escapeHtmlAttr(__('Email')) ?>" value="<?= $block->escapeHtmlAttr($this->helper('Magento\Contact\Helper\Data')->getPostValue('email') ?: $this->helper('Magento\Contact\Helper\Data')->getUserEmail()) ?>" class="input-text" type="email" data-validate="{required:true, 'validate-email':true}"/> + <input name="email" id="email" title="<?= $block->escapeHtmlAttr(__('Email')) ?>" value="<?= $block->escapeHtmlAttr($this->helper(\Magento\Contact\Helper\Data::class)->getPostValue('email') ?: $this->helper(\Magento\Contact\Helper\Data::class)->getUserEmail()) ?>" class="input-text" type="email" data-validate="{required:true, 'validate-email':true}"/> </div> </div> <div class="field telephone"> <label class="label" for="telephone"><span><?= $block->escapeHtml(__('Phone Number')) ?></span></label> <div class="control"> - <input name="telephone" id="telephone" title="<?= $block->escapeHtmlAttr(__('Phone Number')) ?>" value="<?= $block->escapeHtmlAttr($this->helper('Magento\Contact\Helper\Data')->getPostValue('telephone')) ?>" class="input-text" type="text" /> + <input name="telephone" id="telephone" title="<?= $block->escapeHtmlAttr(__('Phone Number')) ?>" value="<?= $block->escapeHtmlAttr($this->helper(\Magento\Contact\Helper\Data::class)->getPostValue('telephone')) ?>" class="input-text" type="text" /> </div> </div> <div class="field comment required"> <label class="label" for="comment"><span><?= $block->escapeHtml(__('What’s on your mind?')) ?></span></label> <div class="control"> - <textarea name="comment" id="comment" title="<?= $block->escapeHtmlAttr(__('What’s on your mind?')) ?>" class="input-text" cols="5" rows="3" data-validate="{required:true}"><?= $block->escapeHtml($this->helper('Magento\Contact\Helper\Data')->getPostValue('comment')) ?></textarea> + <textarea name="comment" id="comment" title="<?= $block->escapeHtmlAttr(__('What’s on your mind?')) ?>" class="input-text" cols="5" rows="3" data-validate="{required:true}"><?= $block->escapeHtml($this->helper(\Magento\Contact\Helper\Data::class)->getPostValue('comment')) ?></textarea> </div> </div> <?= $block->getChildHtml('form.additional.info') ?> diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 3e701128c1b5d..27a3b6fbf4c96 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-store": "101.0.*" }, @@ -25,5 +25,5 @@ "Magento\\Cookie\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml b/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml index c5cfda8cd7d15..8712f31e71b36 100644 --- a/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml +++ b/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml @@ -4,11 +4,9 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Cookie\Block\Html\Notices $block */ ?> -<?php if ($this->helper(\Magento\Cookie\Helper\Cookie::class)->isCookieRestrictionModeEnabled()): ?> +<?php if ($this->helper(\Magento\Cookie\Helper\Cookie::class)->isCookieRestrictionModeEnabled()) : ?> <div role="alertdialog" tabindex="-1" class="message global cookie" diff --git a/app/code/Magento/Cookie/view/frontend/web/js/notices.js b/app/code/Magento/Cookie/view/frontend/web/js/notices.js index f1f3754ea54b1..2c4a070130804 100644 --- a/app/code/Magento/Cookie/view/frontend/web/js/notices.js +++ b/app/code/Magento/Cookie/view/frontend/web/js/notices.js @@ -8,7 +8,7 @@ */ define([ 'jquery', - 'jquery/ui', + 'jquery-ui-modules/widget', 'mage/cookies' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Cookie/view/frontend/web/js/require-cookie.js b/app/code/Magento/Cookie/view/frontend/web/js/require-cookie.js index 8fd329ee0e0e8..0a175136f034e 100644 --- a/app/code/Magento/Cookie/view/frontend/web/js/require-cookie.js +++ b/app/code/Magento/Cookie/view/frontend/web/js/require-cookie.js @@ -8,7 +8,7 @@ */ define([ 'jquery', - 'jquery/ui' + 'jquery-ui-modules/widget' ], function ($) { 'use strict'; diff --git a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php index 4bb5dc196f985..5c8aa1dc78abd 100644 --- a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php +++ b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php @@ -3,16 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - /** * Handling cron jobs */ namespace Magento\Cron\Observer; +use Magento\Cron\Model\Schedule; use Magento\Framework\App\State; use Magento\Framework\Console\Cli; use Magento\Framework\Event\ObserverInterface; -use Magento\Cron\Model\Schedule; use Magento\Framework\Profiler\Driver\Standard\Stat; use Magento\Framework\Profiler\Driver\Standard\StatFactory; @@ -204,7 +203,6 @@ public function __construct( */ public function execute(\Magento\Framework\Event\Observer $observer) { - $currentTime = $this->dateTime->gmtTimestamp(); $jobGroupsRoot = $this->_config->getJobs(); // sort jobs groups to start from used in separated process @@ -258,7 +256,6 @@ function ($groupId) use ($currentTime, $jobsRoot) { */ private function lockGroup($groupId, callable $callback) { - if (!$this->lockManager->lock(self::LOCK_PREFIX . $groupId, self::LOCK_TIMEOUT)) { $this->logger->warning( sprintf( @@ -293,17 +290,20 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, $scheduleLifetime = $scheduleLifetime * self::SECONDS_IN_MINUTE; if ($scheduledTime < $currentTime - $scheduleLifetime) { $schedule->setStatus(Schedule::STATUS_MISSED); + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception(sprintf('Cron Job %s is missed at %s', $jobCode, $schedule->getScheduledAt())); } if (!isset($jobConfig['instance'], $jobConfig['method'])) { $schedule->setStatus(Schedule::STATUS_ERROR); + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception(sprintf('No callbacks found for cron job %s', $jobCode)); } $model = $this->_objectManager->create($jobConfig['instance']); $callback = [$model, $jobConfig['method']]; if (!is_callable($callback)) { $schedule->setStatus(Schedule::STATUS_ERROR); + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception( sprintf('Invalid callback: %s::%s can\'t be called', $jobConfig['instance'], $jobConfig['method']) ); @@ -314,15 +314,18 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, $this->startProfiling(); try { $this->logger->info(sprintf('Cron Job %s is run', $jobCode)); + //phpcs:ignore Magento2.Functions.DiscouragedFunction call_user_func_array($callback, [$schedule]); } catch (\Throwable $e) { $schedule->setStatus(Schedule::STATUS_ERROR); - $this->logger->error(sprintf( - 'Cron Job %s has an error: %s. Statistics: %s', - $jobCode, - $e->getMessage(), - $this->getProfilingStat() - )); + $this->logger->error( + sprintf( + 'Cron Job %s has an error: %s. Statistics: %s', + $jobCode, + $e->getMessage(), + $this->getProfilingStat() + ) + ); if (!$e instanceof \Exception) { $e = new \RuntimeException( 'Error when running a cron job', @@ -335,16 +338,22 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, $this->stopProfiling(); } - $schedule->setStatus(Schedule::STATUS_SUCCESS)->setFinishedAt(strftime( - '%Y-%m-%d %H:%M:%S', - $this->dateTime->gmtTimestamp() - )); + $schedule->setStatus( + Schedule::STATUS_SUCCESS + )->setFinishedAt( + strftime( + '%Y-%m-%d %H:%M:%S', + $this->dateTime->gmtTimestamp() + ) + ); - $this->logger->info(sprintf( - 'Cron Job %s is successfully finished. Statistics: %s', - $jobCode, - $this->getProfilingStat() - )); + $this->logger->info( + sprintf( + 'Cron Job %s is successfully finished. Statistics: %s', + $jobCode, + $this->getProfilingStat() + ) + ); } /** @@ -395,6 +404,28 @@ private function getPendingSchedules($groupId) return $pendingJobs; } + /** + * Return job collection from database with status 'pending', 'running' or 'success' + * + * @param string $groupId + * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + */ + private function getNonExitedSchedules($groupId) + { + $jobs = $this->_config->getJobs(); + $pendingJobs = $this->_scheduleFactory->create()->getCollection(); + $pendingJobs->addFieldToFilter( + 'status', + [ + 'in' => [ + Schedule::STATUS_PENDING, Schedule::STATUS_RUNNING, Schedule::STATUS_SUCCESS + ] + ] + ); + $pendingJobs->addFieldToFilter('job_code', ['in' => array_keys($jobs[$groupId])]); + return $pendingJobs; + } + /** * Generate cron schedule * @@ -426,7 +457,7 @@ private function generateSchedules($groupId) null ); - $schedules = $this->getPendingSchedules($groupId); + $schedules = $this->getNonExitedSchedules($groupId); $exists = []; /** @var Schedule $schedule */ foreach ($schedules as $schedule) { @@ -669,11 +700,14 @@ private function cleanupScheduleMismatches() /** @var \Magento\Cron\Model\ResourceModel\Schedule $scheduleResource */ $scheduleResource = $this->_scheduleFactory->create()->getResource(); foreach ($this->invalid as $jobCode => $scheduledAtList) { - $scheduleResource->getConnection()->delete($scheduleResource->getMainTable(), [ - 'status = ?' => Schedule::STATUS_PENDING, - 'job_code = ?' => $jobCode, - 'scheduled_at in (?)' => $scheduledAtList, - ]); + $scheduleResource->getConnection()->delete( + $scheduleResource->getMainTable(), + [ + 'status = ?' => Schedule::STATUS_PENDING, + 'job_code = ?' => $jobCode, + 'scheduled_at in (?)' => $scheduledAtList, + ] + ); } return $this; } diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index e7d6120af3682..df7a8b2032079 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-store": "101.0.*" }, @@ -25,5 +25,5 @@ "Magento\\Cron\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminSetBaseCurrencyActionGroup.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminSetBaseCurrencyActionGroup.xml new file mode 100644 index 0000000000000..1dc9b83b45008 --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminSetBaseCurrencyActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Set base currency --> + <actionGroup name="AdminSetBaseCurrencyActionGroup" extends="AdminSaveConfigActionGroup"> + <arguments> + <argument name="currency" type="string"/> + </arguments> + <uncheckOption selector="{{CurrencySetupSection.baseCurrencyUseDefault}}" before="clickSaveConfigBtn" stepKey="uncheckUseDefaultOption"/> + <selectOption selector="{{CurrencySetupSection.baseCurrency}}" userInput="{{currency}}" after="uncheckUseDefaultOption" stepKey="setBaseCurrencyField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/CurrencySetupSection.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/CurrencySetupSection.xml index 20fcd1e89360c..5d03c83b50b9d 100644 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/CurrencySetupSection.xml +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/CurrencySetupSection.xml @@ -10,6 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CurrencySetupSection"> <element name="allowCurrencies" type="select" selector="#currency_options_allow"/> + <element name="baseCurrency" type="select" selector="#currency_options_base"/> + <element name="baseCurrencyUseDefault" type="checkbox" selector="#currency_options_base_inherit"/> <element name="currencyOptions" type="select" selector="#currency_options-head"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/CurrencySymbol/composer.json b/app/code/Magento/CurrencySymbol/composer.json index 8ecea9090f9e4..3d5813b4c1f5d 100644 --- a/app/code/Magento/CurrencySymbol/composer.json +++ b/app/code/Magento/CurrencySymbol/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-backend": "101.0.*", "magento/module-config": "101.1.*", @@ -26,5 +26,5 @@ "Magento\\CurrencySymbol\\": "" } }, - "version": "100.3.2" + "version": "100.3.3" } diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml index c0170fb4be5f7..397c2598dc3b0 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml @@ -4,17 +4,15 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile -?> -<?php /** * @var $block \Magento\CurrencySymbol\Block\Adminhtml\System\Currencysymbol */ ?> -<form id="currency-symbols-form" action="<?= $block->escapeHtmlAttr($block->getFormActionUrl()) ?>" method="post"> + +<form id="currency-symbols-form" action="<?= $block->escapeUrl($block->getFormActionUrl()) ?>" method="post"> <input name="form_key" type="hidden" value="<?= $block->escapeHtmlAttr($block->getFormKey()) ?>" /> <fieldset class="admin__fieldset"> - <?php foreach ($block->getCurrencySymbolsData() as $code => $data): ?> + <?php foreach ($block->getCurrencySymbolsData() as $code => $data) : ?> <div class="admin__field _required"> <label class="admin__field-label" for="custom_currency_symbol<?= $block->escapeHtmlAttr($code) ?>"> <span><?= $block->escapeHtml($code) ?> (<?= $block->escapeHtml($data['displayName']) ?>)</span> @@ -32,7 +30,12 @@ <?= $data['inherited'] ? ' checked="checked"' : '' ?> value="1" name="inherit_custom_currency_symbol[<?= $block->escapeHtmlAttr($code) ?>]"> - <label class="admin__field-label" for="custom_currency_symbol_inherit<?= $block->escapeHtmlAttr($code) ?>"><span><?= $block->escapeHtml($block->getInheritText()) ?></span></label> + <label class="admin__field-label" + for="custom_currency_symbol_inherit<?= $block->escapeHtmlAttr($code) ?>"> + <span> + <?= $block->escapeHtml($block->getInheritText()) ?> + </span> + </label> </div> </div> </div> diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/matrix.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/matrix.phtml index 8a16eb71e0853..18b3c7eef746d 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/matrix.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/matrix.phtml @@ -4,63 +4,59 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - -?> -<?php /** * @var $block \Magento\CurrencySymbol\Block\Adminhtml\System\Currency\Rate\Matrix */ -?> -<?php + $_oldRates = $block->getOldRates(); $_newRates = $block->getNewRates(); $_rates = ($_newRates) ? $_newRates : $_oldRates; ?> -<?php if (empty($_rates)): ?> - <div class="message message-warning warning"><p><?= /* @escapeNotVerified */ __('You must first configure currency options before being able to see currency rates.') ?></p></div> -<?php else: ?> - <form name="rateForm" id="rate-form" method="post" action="<?= /* @escapeNotVerified */ $block->getRatesFormAction() ?>"> +<?php if (empty($_rates)) : ?> + <div class="message message-warning warning"><p><?= $block->escapeHtml(__('You must first configure currency options before being able to see currency rates.')) ?></p></div> +<?php else : ?> + <form name="rateForm" id="rate-form" method="post" action="<?= $block->escapeUrl($block->getRatesFormAction()) ?>"> <?= $block->getBlockHtml('formkey') ?> <div class="admin__control-table-wrapper"> <table class="admin__control-table"> <thead> <tr> <th> </th> - <?php $_i = 0; foreach ($block->getAllowedCurrencies() as $_currencyCode): ?> - <th><span><?= /* @escapeNotVerified */ $_currencyCode ?></span></th> + <?php $_i = 0; foreach ($block->getAllowedCurrencies() as $_currencyCode) : ?> + <th><span><?= $block->escapeHtml($_currencyCode) ?></span></th> <?php endforeach; ?> </tr> </thead> - <?php $_j = 0; foreach ($block->getDefaultCurrencies() as $_currencyCode): ?> + <?php $_j = 0; foreach ($block->getDefaultCurrencies() as $_currencyCode) : ?> <tr> - <?php if (isset($_rates[$_currencyCode]) && is_array($_rates[$_currencyCode])): ?> - <?php foreach ($_rates[$_currencyCode] as $_rate => $_value): ?> - <?php if (++$_j == 1): ?> - <td><span class="admin__control-support-text"><?= /* @escapeNotVerified */ $_currencyCode ?></span></td> + <?php if (isset($_rates[$_currencyCode]) && is_array($_rates[$_currencyCode])) : ?> + <?php foreach ($_rates[$_currencyCode] as $_rate => $_value) : ?> + <?php if (++$_j == 1) : ?> + <td><span class="admin__control-support-text"><?= $block->escapeHtml($_currencyCode) ?></span></td> <td> <input type="text" - name="rate[<?= /* @escapeNotVerified */ $_currencyCode ?>][<?= /* @escapeNotVerified */ $_rate ?>]" - value="<?= ($_currencyCode == $_rate) ? '1.0000' : ($_value>0 ? $_value : (isset($_oldRates[$_currencyCode][$_rate]) ? $_oldRates[$_currencyCode][$_rate] : '')) ?>" + name="rate[<?= $block->escapeHtmlAttr($_currencyCode) ?>][<?= $block->escapeHtmlAttr($_rate) ?>]" + value="<?= ($_currencyCode == $_rate) ? '1.0000' : ($_value>0 ? $block->escapeHtmlAttr($_value) : (isset($_oldRates[$_currencyCode][$_rate]) ? $block->escapeHtmlAttr($_oldRates[$_currencyCode][$_rate]) : '')) ?>" class="admin__control-text" <?= ($_currencyCode == $_rate) ? ' disabled' : '' ?> /> - <?php if (isset($_newRates) && $_currencyCode != $_rate && isset($_oldRates[$_currencyCode][$_rate])): ?> - <div class="admin__field-note"><?= /* @escapeNotVerified */ __('Old rate:') ?> <strong><?= /* @escapeNotVerified */ $_oldRates[$_currencyCode][$_rate] ?></strong></div> + <?php if (isset($_newRates) && $_currencyCode != $_rate && isset($_oldRates[$_currencyCode][$_rate])) : ?> + <div class="admin__field-note"><?= $block->escapeHtml(__('Old rate:')) ?> <strong><?= $block->escapeHtml($_oldRates[$_currencyCode][$_rate]) ?></strong></div> <?php endif; ?> </td> - <?php else: ?> + <?php else : ?> <td> <input type="text" - name="rate[<?= /* @escapeNotVerified */ $_currencyCode ?>][<?= /* @escapeNotVerified */ $_rate ?>]" - value="<?= ($_currencyCode == $_rate) ? '1.0000' : ($_value>0 ? $_value : (isset($_oldRates[$_currencyCode][$_rate]) ? $_oldRates[$_currencyCode][$_rate] : '')) ?>" + name="rate[<?= $block->escapeHtmlAttr($_currencyCode) ?>][<?= $block->escapeHtmlAttr($_rate) ?>]" + value="<?= ($_currencyCode == $_rate) ? '1.0000' : ($_value>0 ? $block->escapeHtmlAttr($_value) : (isset($_oldRates[$_currencyCode][$_rate]) ? $block->escapeHtmlAttr($_oldRates[$_currencyCode][$_rate]) : '')) ?>" class="admin__control-text" <?= ($_currencyCode == $_rate) ? ' disabled' : '' ?> /> - <?php if (isset($_newRates) && $_currencyCode != $_rate && isset($_oldRates[$_currencyCode][$_rate])): ?> - <div class="admin__field-note"><?= /* @escapeNotVerified */ __('Old rate:') ?> <strong><?= /* @escapeNotVerified */ $_oldRates[$_currencyCode][$_rate] ?></strong></div> + <?php if (isset($_newRates) && $_currencyCode != $_rate && isset($_oldRates[$_currencyCode][$_rate])) : ?> + <div class="admin__field-note"><?= $block->escapeHtml(__('Old rate:')) ?> <strong><?= $block->escapeHtml($_oldRates[$_currencyCode][$_rate]) ?></strong></div> <?php endif; ?> </td> <?php endif; ?> - <?php endforeach; $_j = 0; ?> + <?php endforeach; ?> + <?php $_j = 0; ?> <?php endif; ?> </tr> <?php endforeach; ?> diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/services.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/services.phtml index 985831c5de79f..471a4a05daa59 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/services.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rate/services.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,7 +10,7 @@ */ ?> <div class="admin__field"> - <label class="admin__field-label"><span><?= /* @escapeNotVerified */ __('Import Service') ?></span></label> + <label class="admin__field-label"><span><?= $block->escapeHtml(__('Import Service')) ?></span></label> <div class="admin__field-control"> <?= $block->getChildHtml('import_services') ?> </div> diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rates.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rates.phtml index 42b2affa2c176..f5390bee3e4e3 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rates.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/system/currency/rates.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <?php /** @@ -13,7 +10,7 @@ */ ?> -<form action="<?= /* @escapeNotVerified */ $block->getImportFormAction() ?>" method="post" class="import-service"> +<form action="<?= $block->escapeUrl($block->getImportFormAction()) ?>" method="post" class="import-service"> <?= $block->getBlockHtml('formkey') ?> <fieldset class="admin__fieldset admin__fieldset-import-service"> <?= $block->getServicesHtml() ?> diff --git a/app/code/Magento/Customer/Block/Account/Dashboard.php b/app/code/Magento/Customer/Block/Account/Dashboard.php index fa9c8f98a8c2c..d263492a66f07 100644 --- a/app/code/Magento/Customer/Block/Account/Dashboard.php +++ b/app/code/Magento/Customer/Block/Account/Dashboard.php @@ -114,10 +114,13 @@ public function getAddressEditUrl($address) * Retrieve the Url for customer orders. * * @return string + * @deprecated 102.0.3 Action does not exist */ public function getOrdersUrl() { - return $this->_urlBuilder->getUrl('customer/order/index', ['_secure' => true]); + //phpcs:ignore Magento2.Functions.DiscouragedFunction + trigger_error('Method is deprecated', E_USER_DEPRECATED); + return ''; } /** @@ -137,7 +140,7 @@ public function getReviewsUrl() */ public function getWishlistUrl() { - return $this->_urlBuilder->getUrl('customer/wishlist/index', ['_secure' => true]); + return $this->_urlBuilder->getUrl('wishlist/index', ['_secure' => true]); } /** diff --git a/app/code/Magento/Customer/Block/Address/Edit.php b/app/code/Magento/Customer/Block/Address/Edit.php index afefb1138deac..ef9937a0cde8b 100644 --- a/app/code/Magento/Customer/Block/Address/Edit.php +++ b/app/code/Magento/Customer/Block/Address/Edit.php @@ -5,7 +5,8 @@ */ namespace Magento\Customer\Block\Address; -use Magento\Framework\Exception\LocalizedException; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; /** @@ -48,7 +49,7 @@ class Edit extends \Magento\Directory\Block\Data protected $dataObjectHelper; /** - * @var \Magento\Customer\Api\AddressMetadataInterface + * @var AddressMetadataInterface */ private $addressMetadata; @@ -67,7 +68,7 @@ class Edit extends \Magento\Directory\Block\Data * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper * @param array $data - * @param \Magento\Customer\Api\AddressMetadataInterface|null $addressMetadata + * @param AddressMetadataInterface|null $addressMetadata * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -84,14 +85,14 @@ public function __construct( \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, array $data = [], - \Magento\Customer\Api\AddressMetadataInterface $addressMetadata = null + AddressMetadataInterface $addressMetadata = null ) { $this->_customerSession = $customerSession; $this->_addressRepository = $addressRepository; $this->addressDataFactory = $addressDataFactory; $this->currentCustomer = $currentCustomer; $this->dataObjectHelper = $dataObjectHelper; - $this->addressMetadata = $addressMetadata; + $this->addressMetadata = $addressMetadata ?: ObjectManager::getInstance()->get(AddressMetadataInterface::class); parent::__construct( $context, $directoryHelper, diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Newsletter/Grid/Renderer/Action.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Newsletter/Grid/Renderer/Action.php index 43d9af36a5652..b9ef3966169a6 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Newsletter/Grid/Renderer/Action.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Newsletter/Grid/Renderer/Action.php @@ -5,11 +5,19 @@ */ namespace Magento\Customer\Block\Adminhtml\Edit\Tab\Newsletter\Grid\Renderer; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; + /** * Adminhtml newsletter queue grid block action item renderer */ class Action extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { + /** + * @var Escaper + */ + private $escaper; + /** * Core registry * @@ -21,17 +29,24 @@ class Action extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Abstract * @param \Magento\Backend\Block\Context $context * @param \Magento\Framework\Registry $registry * @param array $data + * @param Escaper|null $escaper */ public function __construct( \Magento\Backend\Block\Context $context, \Magento\Framework\Registry $registry, - array $data = [] + array $data = [], + Escaper $escaper = null ) { $this->_coreRegistry = $registry; + $this->escaper = $escaper ?? ObjectManager::getInstance()->get( + Escaper::class + ); parent::__construct($context, $data); } /** + * Render actions + * * @param \Magento\Framework\DataObject $row * @return string */ @@ -57,15 +72,20 @@ public function render(\Magento\Framework\DataObject $row) } /** + * Retrieve escaped value + * * @param string $value * @return string */ protected function _getEscapedValue($value) { - return addcslashes(htmlspecialchars($value), '\\\''); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return addcslashes($this->escaper->escapeHtml($value), '\\\''); } /** + * Actions to html + * * @param array $actions * @return string */ diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php index 2fb59ec767e8a..3d4ccb789dc72 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php @@ -107,7 +107,7 @@ protected function _prepareCollection() */ protected function _prepareColumns() { - $this->addColumn('increment_id', ['header' => __('Order'), 'width' => '100', 'index' => 'increment_id']); + $this->addColumn('increment_id', ['header' => __('Order #'), 'width' => '100', 'index' => 'increment_id']); $this->addColumn( 'created_at', @@ -140,7 +140,7 @@ protected function _prepareColumns() $this->addColumn( 'action', [ - 'header' => ' ', + 'header' => 'Action', 'filter' => false, 'sortable' => false, 'width' => '100px', diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Wishlist/Grid/Renderer/Description.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Wishlist/Grid/Renderer/Description.php index d0b47886dca7e..aef91184fc782 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Wishlist/Grid/Renderer/Description.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Wishlist/Grid/Renderer/Description.php @@ -18,6 +18,6 @@ class Description extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Abs */ public function render(\Magento\Framework\DataObject $row) { - return nl2br(htmlspecialchars($row->getData($this->getColumn()->getIndex()))); + return nl2br($this->escapeHtml($row->getData($this->getColumn()->getIndex()))); } } diff --git a/app/code/Magento/Customer/Controller/Account/CreatePassword.php b/app/code/Magento/Customer/Controller/Account/CreatePassword.php index 124ac912a7ccf..d12ec57d3c339 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePassword.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePassword.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Controller\Account; use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ObjectManager; /** * Class CreatePassword @@ -34,20 +38,30 @@ class CreatePassword extends \Magento\Customer\Controller\AbstractAccount implem protected $resultPageFactory; /** - * @param Context $context - * @param Session $customerSession - * @param PageFactory $resultPageFactory - * @param AccountManagementInterface $accountManagement + * @var \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken + */ + private $confirmByToken; + + /** + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\Customer\Api\AccountManagementInterface $accountManagement + * @param \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken $confirmByToken */ public function __construct( Context $context, Session $customerSession, PageFactory $resultPageFactory, - AccountManagementInterface $accountManagement + AccountManagementInterface $accountManagement, + ConfirmCustomerByToken $confirmByToken = null ) { $this->session = $customerSession; $this->resultPageFactory = $resultPageFactory; $this->accountManagement = $accountManagement; + $this->confirmByToken = $confirmByToken + ?? ObjectManager::getInstance()->get(ConfirmCustomerByToken::class); + parent::__construct($context); } @@ -67,6 +81,8 @@ public function execute() try { $this->accountManagement->validateResetPasswordLinkToken(null, $resetPasswordToken); + $this->confirmByToken->execute($resetPasswordToken); + if ($isDirectLink) { $this->session->setRpToken($resetPasswordToken); $resultRedirect = $this->resultRedirectFactory->create(); @@ -77,16 +93,17 @@ public function execute() /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); $resultPage->getLayout() - ->getBlock('resetPassword') - ->setResetPasswordLinkToken($resetPasswordToken); + ->getBlock('resetPassword') + ->setResetPasswordLinkToken($resetPasswordToken); return $resultPage; } } catch (\Exception $exception) { - $this->messageManager->addError(__('Your password reset link has expired.')); + $this->messageManager->addErrorMessage(__('Your password reset link has expired.')); /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('*/*/forgotpassword'); + return $resultRedirect; } } diff --git a/app/code/Magento/Customer/Controller/Account/CreatePost.php b/app/code/Magento/Customer/Controller/Account/CreatePost.php index 79a575add7347..4c9c25b5f33d9 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePost.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePost.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Model\Account\Redirect as AccountRedirect; use Magento\Customer\Api\Data\AddressInterface; @@ -38,6 +39,8 @@ use Magento\Customer\Controller\AbstractAccount; /** + * Post create customer action + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -133,6 +136,11 @@ class CreatePost extends AbstractAccount implements CsrfAwareActionInterface, Ht */ private $formKeyValidator; + /** + * @var CustomerRepository + */ + private $customerRepository; + /** * @param Context $context * @param Session $customerSession @@ -152,6 +160,7 @@ class CreatePost extends AbstractAccount implements CsrfAwareActionInterface, Ht * @param CustomerExtractor $customerExtractor * @param DataObjectHelper $dataObjectHelper * @param AccountRedirect $accountRedirect + * @param CustomerRepository $customerRepository * @param Validator $formKeyValidator * * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -175,6 +184,7 @@ public function __construct( CustomerExtractor $customerExtractor, DataObjectHelper $dataObjectHelper, AccountRedirect $accountRedirect, + CustomerRepository $customerRepository, Validator $formKeyValidator = null ) { $this->session = $customerSession; @@ -195,6 +205,7 @@ public function __construct( $this->dataObjectHelper = $dataObjectHelper; $this->accountRedirect = $accountRedirect; $this->formKeyValidator = $formKeyValidator ?: ObjectManager::getInstance()->get(Validator::class); + $this->customerRepository = $customerRepository; parent::__construct($context); } @@ -328,34 +339,29 @@ public function execute() return $this->resultRedirectFactory->create() ->setUrl($this->_redirect->error($url)); } - $this->session->regenerateId(); - try { $address = $this->extractAddress(); $addresses = $address === null ? [] : [$address]; - $customer = $this->customerExtractor->extract('customer_account_create', $this->_request); $customer->setAddresses($addresses); - $password = $this->getRequest()->getParam('password'); $confirmation = $this->getRequest()->getParam('password_confirmation'); $redirectUrl = $this->session->getBeforeAuthUrl(); - $this->checkPasswordConfirmation($password, $confirmation); - $customer = $this->accountManagement ->createAccount($customer, $password, $redirectUrl); if ($this->getRequest()->getParam('is_subscribed', false)) { - $this->subscriberFactory->create()->subscribeCustomerById($customer->getId()); + $extensionAttributes = $customer->getExtensionAttributes(); + $extensionAttributes->setIsSubscribed(true); + $customer->setExtensionAttributes($extensionAttributes); + $this->customerRepository->save($customer); } - $this->_eventManager->dispatch( 'customer_register_success', ['account_controller' => $this, 'customer' => $customer] ); - $confirmationStatus = $this->accountManagement->getConfirmationStatus($customer->getId()); if ($confirmationStatus === AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED) { $email = $this->customerUrl->getEmailConfirmationUrl($customer->getEmail()); diff --git a/app/code/Magento/Customer/Controller/Account/LoginPost.php b/app/code/Magento/Customer/Controller/Account/LoginPost.php index 04051fbbf366b..4091a068e3094 100644 --- a/app/code/Magento/Customer/Controller/Account/LoginPost.php +++ b/app/code/Magento/Customer/Controller/Account/LoginPost.php @@ -26,6 +26,8 @@ use Magento\Framework\Phrase; /** + * Post login customer action. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class LoginPost extends AbstractAccount implements CsrfAwareActionInterface, HttpPostActionInterface @@ -183,7 +185,6 @@ public function execute() try { $customer = $this->customerAccountManagement->authenticate($login['username'], $login['password']); $this->session->setCustomerDataAsLoggedIn($customer); - $this->session->regenerateId(); if ($this->getCookieManager()->getCookie('mage-cache-sessid')) { $metadata = $this->getCookieMetadataFactory()->createCookieMetadata(); $metadata->setPath('/'); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php index 7220de0356817..eff812a65a3bb 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php @@ -70,6 +70,11 @@ class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionIn */ private $addressRegistry; + /** + * @var \Magento\Framework\Escaper + */ + private $escaper; + /** * @param Action\Context $context * @param CustomerRepositoryInterface $customerRepository @@ -78,6 +83,7 @@ class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionIn * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper * @param \Psr\Log\LoggerInterface $logger * @param AddressRegistry|null $addressRegistry + * @param \Magento\Framework\Escaper $escaper */ public function __construct( Action\Context $context, @@ -86,7 +92,8 @@ public function __construct( \Magento\Customer\Model\Customer\Mapper $customerMapper, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, \Psr\Log\LoggerInterface $logger, - AddressRegistry $addressRegistry = null + AddressRegistry $addressRegistry = null, + \Magento\Framework\Escaper $escaper = null ) { $this->customerRepository = $customerRepository; $this->resultJsonFactory = $resultJsonFactory; @@ -94,6 +101,7 @@ public function __construct( $this->dataObjectHelper = $dataObjectHelper; $this->logger = $logger; $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class); + $this->escaper = $escaper ?: ObjectManager::getInstance()->get(\Magento\Framework\Escaper::class); parent::__construct($context); } @@ -128,10 +136,14 @@ public function execute() $postItems = $this->getRequest()->getParam('items', []); if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { - return $resultJson->setData([ - 'messages' => [__('Please correct the data sent.')], - 'error' => true, - ]); + return $resultJson->setData( + [ + 'messages' => [ + __('Please correct the data sent.') + ], + 'error' => true, + ] + ); } foreach (array_keys($postItems) as $customerId) { @@ -147,10 +159,12 @@ public function execute() $this->getEmailNotification()->credentialsChanged($this->getCustomer(), $currentCustomer->getEmail()); } - return $resultJson->setData([ - 'messages' => $this->getErrorMessages(), - 'error' => $this->isErrorExists() - ]); + return $resultJson->setData( + [ + 'messages' => $this->getErrorMessages(), + 'error' => $this->isErrorExists() + ] + ); } /** @@ -234,13 +248,16 @@ protected function saveCustomer(CustomerInterface $customer) $this->disableAddressValidation($customer); $this->customerRepository->save($customer); } catch (\Magento\Framework\Exception\InputException $e) { - $this->getMessageManager()->addError($this->getErrorWithCustomerId($e->getMessage())); + $this->getMessageManager() + ->addError($this->getErrorWithCustomerId($this->escaper->escapeHtml($e->getMessage()))); $this->logger->critical($e); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->getMessageManager()->addError($this->getErrorWithCustomerId($e->getMessage())); + $this->getMessageManager() + ->addError($this->getErrorWithCustomerId($this->escaper->escapeHtml($e->getMessage()))); $this->logger->critical($e); } catch (\Exception $e) { - $this->getMessageManager()->addError($this->getErrorWithCustomerId('We can\'t save the customer.')); + $this->getMessageManager() + ->addError($this->getErrorWithCustomerId('We can\'t save the customer.')); $this->logger->critical($e); } } diff --git a/app/code/Magento/Customer/Controller/Ajax/Login.php b/app/code/Magento/Customer/Controller/Ajax/Login.php index 5049c83e60f35..1215c47ab9902 100644 --- a/app/code/Magento/Customer/Controller/Ajax/Login.php +++ b/app/code/Magento/Customer/Controller/Ajax/Login.php @@ -191,7 +191,6 @@ public function execute() $credentials['password'] ); $this->customerSession->setCustomerDataAsLoggedIn($customer); - $this->customerSession->regenerateId(); $redirectRoute = $this->getAccountRedirect()->getRedirectCookie(); if ($this->cookieManager->getCookie('mage-cache-sessid')) { $metadata = $this->cookieMetadataFactory->createCookieMetadata(); diff --git a/app/code/Magento/Customer/Helper/Session/CurrentCustomer.php b/app/code/Magento/Customer/Helper/Session/CurrentCustomer.php index e6082de1da4e7..d48ff5918c3f3 100644 --- a/app/code/Magento/Customer/Helper/Session/CurrentCustomer.php +++ b/app/code/Magento/Customer/Helper/Session/CurrentCustomer.php @@ -15,6 +15,7 @@ /** * Class CurrentCustomer + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class CurrentCustomer { diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index ce41d402ec3e6..0f3215f116c95 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -17,8 +17,10 @@ use Magento\Customer\Model\Config\Share as ConfigShare; use Magento\Customer\Model\Customer as CustomerModel; use Magento\Customer\Model\Customer\CredentialsValidator; +use Magento\Customer\Model\ForgotPasswordToken\GetCustomerByToken; use Magento\Customer\Model\Metadata\Validator; use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory; +use Magento\Directory\Model\AllowedCountries; use Magento\Eav\Model\Validator\Attribute\Backend; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Api\SearchCriteriaBuilder; @@ -43,7 +45,6 @@ use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Math\Random; -use Magento\Framework\Phrase; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; use Magento\Framework\Session\SaveHandlerInterface; @@ -339,6 +340,16 @@ class AccountManagement implements AccountManagementInterface */ private $addressRegistry; + /** + * @var AllowedCountries + */ + private $allowedCountriesReader; + + /** + * @var GetCustomerByToken + */ + private $getByToken; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -371,8 +382,12 @@ class AccountManagement implements AccountManagementInterface * @param CollectionFactory|null $visitorCollectionFactory * @param SearchCriteriaBuilder|null $searchCriteriaBuilder * @param AddressRegistry|null $addressRegistry + * @param GetCustomerByToken|null $getByToken + * @param AllowedCountries|null $allowedCountriesReader + * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.LongVariable) */ public function __construct( CustomerFactory $customerFactory, @@ -405,7 +420,9 @@ public function __construct( SaveHandlerInterface $saveHandler = null, CollectionFactory $visitorCollectionFactory = null, SearchCriteriaBuilder $searchCriteriaBuilder = null, - AddressRegistry $addressRegistry = null + AddressRegistry $addressRegistry = null, + GetCustomerByToken $getByToken = null, + AllowedCountries $allowedCountriesReader = null ) { $this->customerFactory = $customerFactory; $this->eventManager = $eventManager; @@ -430,21 +447,26 @@ public function __construct( $this->customerModel = $customerModel; $this->objectFactory = $objectFactory; $this->extensibleDataObjectConverter = $extensibleDataObjectConverter; + $objectManager = ObjectManager::getInstance(); $this->credentialsValidator = - $credentialsValidator ?: ObjectManager::getInstance()->get(CredentialsValidator::class); - $this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()->get(DateTimeFactory::class); - $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + $credentialsValidator ?: $objectManager->get(CredentialsValidator::class); + $this->dateTimeFactory = $dateTimeFactory ?: $objectManager->get(DateTimeFactory::class); + $this->accountConfirmation = $accountConfirmation ?: $objectManager ->get(AccountConfirmation::class); $this->sessionManager = $sessionManager - ?: ObjectManager::getInstance()->get(SessionManagerInterface::class); + ?: $objectManager->get(SessionManagerInterface::class); $this->saveHandler = $saveHandler - ?: ObjectManager::getInstance()->get(SaveHandlerInterface::class); + ?: $objectManager->get(SaveHandlerInterface::class); $this->visitorCollectionFactory = $visitorCollectionFactory - ?: ObjectManager::getInstance()->get(CollectionFactory::class); + ?: $objectManager->get(CollectionFactory::class); $this->searchCriteriaBuilder = $searchCriteriaBuilder - ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); + ?: $objectManager->get(SearchCriteriaBuilder::class); $this->addressRegistry = $addressRegistry - ?: ObjectManager::getInstance()->get(AddressRegistry::class); + ?: $objectManager->get(AddressRegistry::class); + $this->getByToken = $getByToken + ?: $objectManager->get(GetCustomerByToken::class); + $this->allowedCountriesReader = $allowedCountriesReader + ?: $objectManager->get(AllowedCountries::class); } /** @@ -510,8 +532,11 @@ public function activateById($customerId, $confirmationKey) * @param \Magento\Customer\Api\Data\CustomerInterface $customer * @param string $confirmationKey * @return \Magento\Customer\Api\Data\CustomerInterface - * @throws \Magento\Framework\Exception\State\InvalidTransitionException - * @throws \Magento\Framework\Exception\State\InputMismatchException + * @throws InputException + * @throws InputMismatchException + * @throws InvalidTransitionException + * @throws LocalizedException + * @throws NoSuchEntityException */ private function activateCustomer($customer, $confirmationKey) { @@ -619,42 +644,6 @@ public function initiatePasswordReset($email, $template, $websiteId = null) return false; } - /** - * Match a customer by their RP token. - * - * @param string $rpToken - * @throws ExpiredException - * @throws NoSuchEntityException - * @return CustomerInterface - * @throws LocalizedException - */ - private function matchCustomerByRpToken(string $rpToken): CustomerInterface - { - $this->searchCriteriaBuilder->addFilter( - 'rp_token', - $rpToken - ); - $this->searchCriteriaBuilder->setPageSize(1); - $found = $this->customerRepository->getList( - $this->searchCriteriaBuilder->create() - ); - if ($found->getTotalCount() > 1) { - //Failed to generated unique RP token - throw new ExpiredException( - new Phrase('Reset password token expired.') - ); - } - if ($found->getTotalCount() === 0) { - //Customer with such token not found. - throw NoSuchEntityException::singleField( - 'rp_token', - $rpToken - ); - } - //Unique customer found. - return $found->getItems()[0]; - } - /** * Handle not supported template * @@ -663,15 +652,17 @@ private function matchCustomerByRpToken(string $rpToken): CustomerInterface */ private function handleUnknownTemplate($template) { - throw new InputException(__( - 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', - [ - 'value' => $template, - 'fieldName' => 'template', - 'template1' => AccountManagement::EMAIL_REMINDER, - 'template2' => AccountManagement::EMAIL_RESET - ] - )); + throw new InputException( + __( + 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', + [ + 'value' => $template, + 'fieldName' => 'template', + 'template1' => AccountManagement::EMAIL_REMINDER, + 'template2' => AccountManagement::EMAIL_RESET + ] + ) + ); } /** @@ -680,7 +671,7 @@ private function handleUnknownTemplate($template) public function resetPassword($email, $resetToken, $newPassword) { if (!$email) { - $customer = $this->matchCustomerByRpToken($resetToken); + $customer = $this->getByToken->execute($resetToken); $email = $customer->getEmail(); } else { $customer = $this->customerRepository->get($email); @@ -819,6 +810,8 @@ public function getConfirmationStatus($customerId) /** * @inheritdoc + * + * @throws LocalizedException */ public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') { @@ -841,6 +834,8 @@ public function createAccount(CustomerInterface $customer, $password = null, $re /** * @inheritdoc + * + * @throws InputMismatchException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -901,6 +896,9 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash } try { foreach ($customerAddresses as $address) { + if (!$this->isAddressAllowedForWebsite($address, $customer->getStoreId())) { + continue; + } if ($address->getId()) { $newAddress = clone $address; $newAddress->setId(null); @@ -963,6 +961,7 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU $templateType = self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD; } $this->getEmailNotification()->newAccount($customer, $templateType, $redirectUrl, $customer->getStoreId()); + $customer->setConfirmation(null); } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored $this->logger->critical($e); @@ -973,6 +972,8 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU /** * @inheritdoc + * + * @throws InvalidEmailOrPasswordException */ public function changePassword($email, $currentPassword, $newPassword) { @@ -986,6 +987,8 @@ public function changePassword($email, $currentPassword, $newPassword) /** * @inheritdoc + * + * @throws InvalidEmailOrPasswordException */ public function changePasswordById($customerId, $currentPassword, $newPassword) { @@ -1075,7 +1078,7 @@ public function validate(CustomerInterface $customer) $result = $this->getEavValidator()->isValid($customerModel); if ($result === false && is_array($this->getEavValidator()->getMessages())) { return $validationResults->setIsValid(false)->setMessages( - // phpcs:ignore Magento2.Functions.DiscouragedFunction + // phpcs:ignore Magento2.Functions.DiscouragedFunction call_user_func_array( 'array_merge', $this->getEavValidator()->getMessages() @@ -1123,12 +1126,14 @@ public function isCustomerInStore($customerWebsiteId, $storeId) * * @param int $customerId * @param string $resetPasswordLinkToken + * * @return bool - * @throws \Magento\Framework\Exception\State\InputMismatchException If token is mismatched - * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired - * @throws \Magento\Framework\Exception\InputException If token or customer id is invalid - * @throws \Magento\Framework\Exception\NoSuchEntityException If customer doesn't exist + * @throws ExpiredException If token is expired + * @throws InputException If token or customer id is invalid + * @throws InputMismatchException If token is mismatched * @throws LocalizedException + * @throws NoSuchEntityException If customer doesn't exist + * @SuppressWarnings(PHPMD.LongVariable) */ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken) { @@ -1143,7 +1148,8 @@ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken if ($customerId === null) { //Looking for the customer. - $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken) + $customerId = $this->getByToken + ->execute($resetPasswordLinkToken) ->getId(); } if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) { @@ -1311,13 +1317,20 @@ protected function sendEmailTemplate( } $transport = $this->transportBuilder->setTemplateIdentifier($templateId) - ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) + ->setTemplateOptions( + [ + 'area' => Area::AREA_FRONTEND, + 'store' => $storeId + ] + ) ->setTemplateVars($templateParams) - ->setFrom($this->scopeConfig->getValue( - $sender, - ScopeInterface::SCOPE_STORE, - $storeId - )) + ->setFrom( + $this->scopeConfig->getValue( + $sender, + ScopeInterface::SCOPE_STORE, + $storeId + ) + ) ->addTo($email, $this->customerViewHelper->getCustomerName($customer)) ->getTransport(); @@ -1611,4 +1624,18 @@ private function setIgnoreValidationFlag($customer) { $customer->setData('ignore_validation_flag', true); } + + /** + * Check is address allowed for store + * + * @param AddressInterface $address + * @param int|null $storeId + * @return bool + */ + private function isAddressAllowedForWebsite(AddressInterface $address, $storeId): bool + { + $allowedCountries = $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_STORE, $storeId); + + return in_array($address->getCountryId(), $allowedCountries); + } } diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 5e42e42650456..1aa3ddcc621c8 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -375,6 +375,8 @@ public function getRegion() } } elseif (is_string($region)) { $this->setData('region', $region); + } elseif (!$regionId && is_array($region)) { + $this->setData('region', $regionId); } return $this->getData('region'); diff --git a/app/code/Magento/Customer/Model/Address/DataProvider.php b/app/code/Magento/Customer/Model/Address/DataProvider.php index e1dd68207cae5..e7f9fce021709 100644 --- a/app/code/Magento/Customer/Model/Address/DataProvider.php +++ b/app/code/Magento/Customer/Model/Address/DataProvider.php @@ -19,6 +19,7 @@ /** * Dataprovider of customer addresses for customer address grid. + * * @property \Magento\Customer\Model\ResourceModel\Address\Collection $collection */ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider @@ -222,8 +223,7 @@ private function getAttributesMeta(Type $entityType): array $meta[$attribute->getAttributeCode()] = $this->attributeMetadataResolver->getAttributesMeta( $attribute, $entityType, - $this->allowToShowHiddenAttributes, - $this->getRequestFieldName() + $this->allowToShowHiddenAttributes ); } $this->attributeMetadataResolver->processWebsiteMeta($meta); diff --git a/app/code/Magento/Customer/Model/Address/Validator/Country.php b/app/code/Magento/Customer/Model/Address/Validator/Country.php index fdc5510d1d2e0..e0eb1cbadd862 100644 --- a/app/code/Magento/Customer/Model/Address/Validator/Country.php +++ b/app/code/Magento/Customer/Model/Address/Validator/Country.php @@ -9,6 +9,8 @@ use Magento\Customer\Model\Address\ValidatorInterface; use Magento\Directory\Helper\Data; use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; use Magento\Store\Model\ScopeInterface; /** @@ -16,6 +18,11 @@ */ class Country implements ValidatorInterface { + /** + * @var Escaper + */ + private $escaper; + /** * @var Data */ @@ -29,13 +36,18 @@ class Country implements ValidatorInterface /** * @param Data $directoryData * @param AllowedCountries $allowedCountriesReader + * @param Escaper|null $escaper */ public function __construct( Data $directoryData, - AllowedCountries $allowedCountriesReader + AllowedCountries $allowedCountriesReader, + Escaper $escaper = null ) { $this->directoryData = $directoryData; $this->allowedCountriesReader = $allowedCountriesReader; + $this->escaper = $escaper ?? ObjectManager::getInstance()->get( + Escaper::class + ); } /** @@ -67,7 +79,7 @@ private function validateCountry(AbstractAddress $address) //Checking if such country exists. $errors[] = __( 'Invalid value of "%value" provided for the %fieldName field.', - ['fieldName' => 'countryId', 'value' => htmlspecialchars($countryId)] + ['fieldName' => 'countryId', 'value' => $this->escaper->escapeHtml($countryId)] ); } @@ -104,7 +116,7 @@ private function validateRegion(AbstractAddress $address) //If a region is selected then checking if it exists. $errors[] = __( 'Invalid value of "%value" provided for the %fieldName field.', - ['fieldName' => 'regionId', 'value' => htmlspecialchars($regionId)] + ['fieldName' => 'regionId', 'value' => $this->escaper->escapeHtml($regionId)] ); } diff --git a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php index c22cc9a4f23f4..acc54424c3acc 100644 --- a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php +++ b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php @@ -101,21 +101,24 @@ public function __construct( * @param AbstractAttribute $attribute * @param Type $entityType * @param bool $allowToShowHiddenAttributes - * @param string $requestFieldName * @return array * @throws \Magento\Framework\Exception\LocalizedException */ public function getAttributesMeta( AbstractAttribute $attribute, Type $entityType, - bool $allowToShowHiddenAttributes, - string $requestFieldName + bool $allowToShowHiddenAttributes ): array { $meta = $this->modifyBooleanAttributeMeta($attribute); // use getDataUsingMethod, since some getters are defined and apply additional processing of returning value foreach (self::$metaProperties as $metaName => $origName) { $value = $attribute->getDataUsingMethod($origName); - $meta['arguments']['data']['config'][$metaName] = ($metaName === 'label') ? __($value) : $value; + if ($metaName === 'label') { + $meta['arguments']['data']['config'][$metaName] = __($value); + $meta['arguments']['data']['config']['__disableTmpl'] = [$metaName => true]; + } else { + $meta['arguments']['data']['config'][$metaName] = $value; + } if ('frontend_input' === $origName) { $meta['arguments']['data']['config']['formElement'] = self::$formElement[$value] ?? $value; } @@ -126,7 +129,14 @@ public function getAttributesMeta( $meta['arguments']['data']['config']['options'] = $this->countryWithWebsiteSource ->getAllOptions(); } else { - $meta['arguments']['data']['config']['options'] = $attribute->getSource()->getAllOptions(); + $options = $attribute->getSource()->getAllOptions(); + array_walk( + $options, + function (&$item) { + $item['__disableTmpl'] = ['label' => true]; + } + ); + $meta['arguments']['data']['config']['options'] = $options; } } @@ -138,7 +148,6 @@ public function getAttributesMeta( $meta['arguments']['data']['config']['componentType'] = Field::NAME; $meta['arguments']['data']['config']['visible'] = $this->canShowAttribute( $attribute, - $requestFieldName, $allowToShowHiddenAttributes ); @@ -147,7 +156,6 @@ public function getAttributesMeta( $attribute, $meta['arguments']['data']['config'] ); - return $meta; } @@ -155,48 +163,16 @@ public function getAttributesMeta( * Detect can we show attribute on specific form or not * * @param AbstractAttribute $customerAttribute - * @param string $requestFieldName * @param bool $allowToShowHiddenAttributes * @return bool */ private function canShowAttribute( AbstractAttribute $customerAttribute, - string $requestFieldName, bool $allowToShowHiddenAttributes ) { - $userDefined = (bool)$customerAttribute->getIsUserDefined(); - if (!$userDefined) { - return $customerAttribute->getIsVisible(); - } - - $canShowOnForm = $this->canShowAttributeInForm($customerAttribute, $requestFieldName); - - return ($allowToShowHiddenAttributes && $canShowOnForm) || - (!$allowToShowHiddenAttributes && $canShowOnForm && $customerAttribute->getIsVisible()); - } - - /** - * Check whether the specific attribute can be shown in form: customer registration, customer edit, etc... - * - * @param AbstractAttribute $customerAttribute - * @param string $requestFieldName - * @return bool - */ - private function canShowAttributeInForm(AbstractAttribute $customerAttribute, string $requestFieldName): bool - { - $isRegistration = $this->context->getRequestParam($requestFieldName) === null; - - if ($customerAttribute->getEntityType()->getEntityTypeCode() === 'customer') { - return \is_array($customerAttribute->getUsedInForms()) && - ( - (\in_array('customer_account_create', $customerAttribute->getUsedInForms(), true) - && $isRegistration) || - (\in_array('customer_account_edit', $customerAttribute->getUsedInForms(), true) - && !$isRegistration) - ); - } - return \is_array($customerAttribute->getUsedInForms()) && - \in_array('customer_address_edit', $customerAttribute->getUsedInForms(), true); + return $allowToShowHiddenAttributes && (bool) $customerAttribute->getIsUserDefined() + ? true + : (bool) $customerAttribute->getIsVisible(); } /** diff --git a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php index c19f1e68256cc..53c1b470a5175 100644 --- a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php +++ b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php @@ -14,6 +14,9 @@ use Magento\Customer\Model\Form; use Magento\Store\Model\ScopeInterface; +/** + * Provides some configurations for customer. + */ class ConfigProvider implements ConfigProviderInterface { /** @@ -57,7 +60,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig() { @@ -96,12 +99,14 @@ protected function getLoginUrl() * Whether redirect to login page is required * * @return bool + * + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function isRedirectRequired() { $baseUrl = $this->storeManager->getStore()->getBaseUrl(); - if (strpos($this->getLoginUrl(), $baseUrl) !== false) { + if (strpos($this->getLoginUrl(), (string) $baseUrl) !== false) { return false; } diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 58ab3c730d443..94b850eb8e5ab 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -20,6 +20,7 @@ use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Store\Model\ScopeInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Math\Random; /** * Customer model @@ -180,7 +181,7 @@ class Customer extends \Magento\Framework\Model\AbstractModel protected $_encryptor; /** - * @var \Magento\Framework\Math\Random + * @var Random */ protected $mathRandom; @@ -248,6 +249,7 @@ class Customer extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data * @param AccountConfirmation|null $accountConfirmation + * @param Random|null $mathRandom * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -272,7 +274,8 @@ public function __construct( \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - AccountConfirmation $accountConfirmation = null + AccountConfirmation $accountConfirmation = null, + Random $mathRandom = null ) { $this->metadataService = $metadataService; $this->_scopeConfig = $scopeConfig; @@ -291,6 +294,7 @@ public function __construct( $this->indexerRegistry = $indexerRegistry; $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() ->get(AccountConfirmation::class); + $this->mathRandom = $mathRandom ?: ObjectManager::getInstance()->get(Random::class); parent::__construct( $context, $registry, @@ -805,7 +809,7 @@ public function isConfirmationRequired() */ public function getRandomConfirmationKey() { - return md5(uniqid()); + return $this->mathRandom->getRandomString(32); } /** diff --git a/app/code/Magento/Customer/Model/Customer/DataProvider.php b/app/code/Magento/Customer/Model/Customer/DataProvider.php index 482478acbb291..d957a457cc2f9 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProvider.php +++ b/app/code/Magento/Customer/Model/Customer/DataProvider.php @@ -307,45 +307,17 @@ protected function getAttributesMeta(Type $entityType) return $meta; } - /** - * Check whether the specific attribute can be shown in form: customer registration, customer edit, etc... - * - * @param Attribute $customerAttribute - * @return bool - */ - private function canShowAttributeInForm(AbstractAttribute $customerAttribute) - { - $isRegistration = $this->context->getRequestParam($this->getRequestFieldName()) === null; - - if ($customerAttribute->getEntityType()->getEntityTypeCode() === 'customer') { - return is_array($customerAttribute->getUsedInForms()) && - ( - (in_array('customer_account_create', $customerAttribute->getUsedInForms()) && $isRegistration) || - (in_array('customer_account_edit', $customerAttribute->getUsedInForms()) && !$isRegistration) - ); - } else { - return is_array($customerAttribute->getUsedInForms()) && - in_array('customer_address_edit', $customerAttribute->getUsedInForms()); - } - } - /** * Detect can we show attribute on specific form or not * * @param Attribute $customerAttribute * @return bool */ - private function canShowAttribute(AbstractAttribute $customerAttribute) + private function canShowAttribute(AbstractAttribute $customerAttribute): bool { - $userDefined = (bool) $customerAttribute->getIsUserDefined(); - if (!$userDefined) { - return $customerAttribute->getIsVisible(); - } - - $canShowOnForm = $this->canShowAttributeInForm($customerAttribute); - - return ($this->allowToShowHiddenAttributes && $canShowOnForm) || - (!$this->allowToShowHiddenAttributes && $canShowOnForm && $customerAttribute->getIsVisible()); + return $this->allowToShowHiddenAttributes && (bool) $customerAttribute->getIsUserDefined() + ? true + : (bool) $customerAttribute->getIsVisible(); } /** diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php index 07b8681df91ac..6d18b881a69ff 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php +++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php @@ -188,8 +188,7 @@ private function getAttributesMeta(Type $entityType): array $meta[$attribute->getAttributeCode()] = $this->attributeMetadataResolver->getAttributesMeta( $attribute, $entityType, - $this->allowToShowHiddenAttributes, - $this->getRequestFieldName() + $this->allowToShowHiddenAttributes ); } $this->attributeMetadataResolver->processWebsiteMeta($meta); diff --git a/app/code/Magento/Customer/Model/Customer/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Source/Group.php index e4c1d2e75be22..1064152b20fd5 100644 --- a/app/code/Magento/Customer/Model/Customer/Source/Group.php +++ b/app/code/Magento/Customer/Model/Customer/Source/Group.php @@ -11,6 +11,9 @@ use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; +/** + * Group. + */ class Group implements GroupSourceInterface { /** diff --git a/app/code/Magento/Customer/Model/CustomerAuthUpdate.php b/app/code/Magento/Customer/Model/CustomerAuthUpdate.php index 06de649524e71..bc9bffb6ffdf0 100644 --- a/app/code/Magento/Customer/Model/CustomerAuthUpdate.php +++ b/app/code/Magento/Customer/Model/CustomerAuthUpdate.php @@ -6,31 +6,43 @@ namespace Magento\Customer\Model; +use Magento\Customer\Model\ResourceModel\Customer as CustomerResourceModel; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; + /** * Customer Authentication update model. */ class CustomerAuthUpdate { /** - * @var \Magento\Customer\Model\CustomerRegistry + * @var CustomerRegistry */ protected $customerRegistry; /** - * @var \Magento\Customer\Model\ResourceModel\Customer + * @var CustomerResourceModel */ protected $customerResourceModel; /** - * @param \Magento\Customer\Model\CustomerRegistry $customerRegistry - * @param \Magento\Customer\Model\ResourceModel\Customer $customerResourceModel + * @var Customer + */ + private $customerModel; + + /** + * @param CustomerRegistry $customerRegistry + * @param CustomerResourceModel $customerResourceModel + * @param Customer|null $customerModel */ public function __construct( - \Magento\Customer\Model\CustomerRegistry $customerRegistry, - \Magento\Customer\Model\ResourceModel\Customer $customerResourceModel + CustomerRegistry $customerRegistry, + CustomerResourceModel $customerResourceModel, + Customer $customerModel = null ) { $this->customerRegistry = $customerRegistry; $this->customerResourceModel = $customerResourceModel; + $this->customerModel = $customerModel ?: ObjectManager::getInstance()->get(Customer::class); } /** @@ -38,21 +50,30 @@ public function __construct( * * @param int $customerId * @return $this + * @throws NoSuchEntityException */ public function saveAuth($customerId) { $customerSecure = $this->customerRegistry->retrieveSecureData($customerId); + $this->customerResourceModel->load($this->customerModel, $customerId); + $currentLockExpiresVal = $this->customerModel->getData('lock_expires'); + $newLockExpiresVal = $customerSecure->getData('lock_expires'); + $this->customerResourceModel->getConnection()->update( $this->customerResourceModel->getTable('customer_entity'), [ 'failures_num' => $customerSecure->getData('failures_num'), 'first_failure' => $customerSecure->getData('first_failure'), - 'lock_expires' => $customerSecure->getData('lock_expires'), + 'lock_expires' => $newLockExpiresVal, ], $this->customerResourceModel->getConnection()->quoteInto('entity_id = ?', $customerId) ); + if ($currentLockExpiresVal !== $newLockExpiresVal) { + $this->customerModel->reindex(); + } + return $this; } } diff --git a/app/code/Magento/Customer/Model/CustomerIdProvider.php b/app/code/Magento/Customer/Model/CustomerIdProvider.php new file mode 100644 index 0000000000000..e670d5eb02d9c --- /dev/null +++ b/app/code/Magento/Customer/Model/CustomerIdProvider.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model; + +use Magento\Framework\App\RequestInterface; + +/** + * Provides customer id from request. + */ +class CustomerIdProvider +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @param RequestInterface $request + */ + public function __construct( + RequestInterface $request + ) { + $this->request = $request; + } + + /** + * Get customer id from request. + * + * @return int + */ + public function getCustomerId(): int + { + return (int)$this->request->getParam('id'); + } +} diff --git a/app/code/Magento/Customer/Model/Data/Address.php b/app/code/Magento/Customer/Model/Data/Address.php index f4cf228fb557c..b399a144368d3 100644 --- a/app/code/Magento/Customer/Model/Data/Address.php +++ b/app/code/Magento/Customer/Model/Data/Address.php @@ -42,7 +42,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function getCustomAttributesCodes() { @@ -327,7 +327,7 @@ public function setCompany($company) */ public function setTelephone($telephone) { - return $this->setData(self::TELEPHONE, $telephone); + return $this->setData(self::TELEPHONE, trim($telephone)); } /** @@ -452,7 +452,7 @@ public function setIsDefaultBilling($isDefaultBilling) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Customer\Api\Data\AddressExtensionInterface|null */ @@ -462,7 +462,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Customer\Api\Data\AddressExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Customer/Model/Delegation/Storage.php b/app/code/Magento/Customer/Model/Delegation/Storage.php index 71a61d59057cb..d1eae3f56001c 100644 --- a/app/code/Magento/Customer/Model/Delegation/Storage.php +++ b/app/code/Magento/Customer/Model/Delegation/Storage.php @@ -19,6 +19,7 @@ use Magento\Customer\Model\Delegation\Data\NewOperationFactory; use Magento\Customer\Api\Data\CustomerInterfaceFactory; use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Framework\Api\CustomAttributesDataInterface; use Psr\Log\LoggerInterface; /** @@ -100,11 +101,13 @@ public function storeNewOperation(CustomerInterface $customer, array $delegatedD } } $this->session->setCustomerFormData($customerData); - $this->session->setDelegatedNewCustomerData([ - 'customer' => $customerData, - 'addresses' => $addressesData, - 'delegated_data' => $delegatedData, - ]); + $this->session->setDelegatedNewCustomerData( + [ + 'customer' => $customerData, + 'addresses' => $addressesData, + 'delegated_data' => $delegatedData, + ] + ); } /** @@ -134,18 +137,31 @@ public function consumeNewOperation() ); $addressData['region'] = $region; } - $addresses[] = $this->addressFactory->create( + + $customAttributes = []; + if (!empty($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) { + $customAttributes = $addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES]; + unset($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES]); + } + + $address = $this->addressFactory->create( ['data' => $addressData] ); + + foreach ($customAttributes as $attributeCode => $attributeValue) { + $address->setCustomAttribute($attributeCode, $attributeValue); + } + + $addresses[] = $address; } $customerData = $serialized['customer']; $customerData['addresses'] = $addresses; - return $this->newFactory->create([ - 'customer' => $this->customerFactory->create( - ['data' => $customerData] - ), - 'additionalData' => $serialized['delegated_data'], - ]); + return $this->newFactory->create( + [ + 'customer' => $this->customerFactory->create(['data' => $customerData]), + 'additionalData' => $serialized['delegated_data'], + ] + ); } } diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index 144c24f8e8355..573f86247e0c3 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -362,7 +362,7 @@ public function passwordResetConfirmation(CustomerInterface $customer) * @param CustomerInterface $customer * @param string $type * @param string $backUrl - * @param string $storeId + * @param int $storeId * @param string $sendemailStoreId * @return void * @throws LocalizedException diff --git a/app/code/Magento/Customer/Model/EmailNotificationInterface.php b/app/code/Magento/Customer/Model/EmailNotificationInterface.php index 0821abff04750..028322acdd9d7 100644 --- a/app/code/Magento/Customer/Model/EmailNotificationInterface.php +++ b/app/code/Magento/Customer/Model/EmailNotificationInterface.php @@ -73,7 +73,7 @@ public function passwordResetConfirmation(CustomerInterface $customer); * @param CustomerInterface $customer * @param string $type * @param string $backUrl - * @param string $storeId + * @param int $storeId * @param string $sendemailStoreId * @return void * @throws LocalizedException diff --git a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php index 535bfe97bc457..b9e9125724894 100644 --- a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php +++ b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php @@ -88,9 +88,7 @@ private function getFileUploaderData( $file = $customerData[$attributeCode] ?? null; /** @var FileProcessor $fileProcessor */ - $fileProcessor = $this->fileProcessorFactory->create([ - 'entityTypeCode' => $entityType->getEntityTypeCode(), - ]); + $fileProcessor = $this->fileProcessorFactory->create(['entityTypeCode' => $entityType->getEntityTypeCode()]); if (!empty($file) && $fileProcessor->isExist($file) @@ -103,6 +101,7 @@ private function getFileUploaderData( 'file' => $file, 'size' => null !== $stat ? $stat['size'] : 0, 'url' => $viewUrl ?? '', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'name' => basename($file), 'type' => $fileProcessor->getMimeType($file), ], @@ -138,9 +137,12 @@ public function overrideFileUploaderMetadata( if (isset($config['validation']['file_extensions'])) { $allowedExtensions = explode(',', $config['validation']['file_extensions']); - array_walk($allowedExtensions, function (&$value) { - $value = strtolower(trim($value)); - }); + array_walk( + $allowedExtensions, + function (&$value) { + $value = strtolower(trim($value)); + } + ); } $allowedExtensions = implode(' ', $allowedExtensions); @@ -149,6 +151,7 @@ public function overrideFileUploaderMetadata( $url = $this->getFileUploadUrl($entityTypeCode); $config = [ + 'dataType' => $this->getMetadataValue($config, 'dataType'), 'formElement' => 'fileUploader', 'componentType' => 'fileUploader', 'maxFileSize' => $maxFileSize, diff --git a/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php b/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php new file mode 100644 index 0000000000000..6aadc814a4b9b --- /dev/null +++ b/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\ForgotPasswordToken; + +use Magento\Customer\Api\CustomerRepositoryInterface; + +/** + * Confirm customer by reset password token + */ +class ConfirmCustomerByToken +{ + /** + * @var GetCustomerByToken + */ + private $getByToken; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * ConfirmByToken constructor. + * + * @param GetCustomerByToken $getByToken + * @param CustomerRepositoryInterface $customerRepository + */ + public function __construct( + GetCustomerByToken $getByToken, + CustomerRepositoryInterface $customerRepository + ) { + $this->getByToken = $getByToken; + $this->customerRepository = $customerRepository; + } + + /** + * Confirm customer account my rp_token + * + * @param string $resetPasswordToken + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(string $resetPasswordToken): void + { + $customer = $this->getByToken->execute($resetPasswordToken); + if ($customer->getConfirmation()) { + $this->customerRepository->save( + $customer->setConfirmation(null) + ); + } + } +} diff --git a/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php new file mode 100644 index 0000000000000..09af4e296bd92 --- /dev/null +++ b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\ForgotPasswordToken; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\State\ExpiredException; +use Magento\Framework\Phrase; + +/** + * Get Customer By reset password token + * @SuppressWarnings(PHPMD.LongVariable) + */ +class GetCustomerByToken +{ + /** + * @var \Magento\Customer\Api\CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var \Magento\Framework\Api\SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * ForgotPassword constructor. + * + * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository + * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + */ + public function __construct( + CustomerRepositoryInterface $customerRepository, + SearchCriteriaBuilder $searchCriteriaBuilder + ) { + $this->customerRepository = $customerRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * Get customer by rp_token + * + * @param string $resetPasswordToken + * + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws ExpiredException + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(string $resetPasswordToken):CustomerInterface + { + $this->searchCriteriaBuilder->addFilter( + 'rp_token', + $resetPasswordToken + ); + $this->searchCriteriaBuilder->setPageSize(1); + $found = $this->customerRepository->getList( + $this->searchCriteriaBuilder->create() + ); + + if ($found->getTotalCount() > 1) { + //Failed to generated unique RP token + throw new ExpiredException( + new Phrase('Reset password token expired.') + ); + } + if ($found->getTotalCount() === 0) { + //Customer with such token not found. + new NoSuchEntityException( + new Phrase( + 'No such entity with rp_token = %value', + [ + 'value' => $resetPasswordToken + ] + ) + ); + } + + //Unique customer found. + return $found->getItems()[0]; + } +} diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerNotification.php b/app/code/Magento/Customer/Model/Plugin/CustomerNotification.php index 517aef5690ee6..577c97a19268a 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerNotification.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerNotification.php @@ -15,6 +15,11 @@ use Magento\Framework\Exception\NoSuchEntityException; use Psr\Log\LoggerInterface; +/** + * Plugin before \Magento\Framework\App\Action\AbstractAction::dispatch. + * + * Plugin to remove notifications from cache. + */ class CustomerNotification { /** @@ -66,6 +71,8 @@ public function __construct( } /** + * Removes notifications from cache. + * * @param AbstractAction $subject * @param RequestInterface $request * @return void @@ -82,10 +89,10 @@ public function beforeDispatch(AbstractAction $subject, RequestInterface $reques ) ) { try { + $this->session->regenerateId(); $customer = $this->customerRepository->getById($customerId); $this->session->setCustomerData($customer); $this->session->setCustomerGroupId($customer->getGroupId()); - $this->session->regenerateId(); $this->notificationStorage->remove(NotificationStorage::UPDATE_CUSTOMER_SESSION, $customer->getId()); } catch (NoSuchEntityException $e) { $this->logger->error($e); diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php index 2eb1ef897e70e..94196df6fe093 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php @@ -95,9 +95,12 @@ protected function _getDefaultAttributes() /** * Check customer scope, email and confirmation key before saving * - * @param \Magento\Framework\DataObject $customer + * @param \Magento\Framework\DataObject|\Magento\Customer\Api\Data\CustomerInterface $customer + * * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws AlreadyExistsException + * @throws ValidatorException + * @throws \Magento\Framework\Exception\NoSuchEntityException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -141,9 +144,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer) } // set confirmation key logic - if ($customer->getForceConfirmed() || $customer->getPasswordHash() == '') { - $customer->setConfirmation(null); - } elseif (!$customer->getId() && $customer->isConfirmationRequired()) { + if (!$customer->getId() && $customer->isConfirmationRequired()) { $customer->setConfirmation($customer->getRandomConfirmationKey()); } // remove customer confirmation key from database, if empty @@ -163,7 +164,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer) * * @param \Magento\Customer\Model\Customer $customer * @return void - * @throws \Magento\Framework\Validator\Exception + * @throws ValidatorException */ protected function _validate($customer) { diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index 82d12b364add5..51e7edb98c907 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -209,7 +209,9 @@ public function save(CustomerInterface $customer, $passwordHash = null) $customerModel->setId($customer->getId()); $storeId = $customerModel->getStoreId(); if ($storeId === null) { - $customerModel->setStoreId($this->storeManager->getStore()->getId()); + $customerModel->setStoreId( + $prevCustomerData ? $prevCustomerData->getStoreId() : $this->storeManager->getStore()->getId() + ); } // Need to use attribute set or future updates can cause data loss if (!$customerModel->getAttributeSetId()) { @@ -277,7 +279,6 @@ public function save(CustomerInterface $customer, $passwordHash = null) 'delegate_data' => $delegatedNewOperation ? $delegatedNewOperation->getAdditionalData() : [], ] ); - return $savedCustomer; } @@ -343,7 +344,7 @@ public function getById($customerId) * Retrieve customers which match a specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Customer/Model/Session.php b/app/code/Magento/Customer/Model/Session.php index 5900fed218edf..ca15b8ab2034a 100644 --- a/app/code/Magento/Customer/Model/Session.php +++ b/app/code/Magento/Customer/Model/Session.php @@ -17,6 +17,7 @@ * @api * @method string getNoReferer() * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Session extends \Magento\Framework\Session\SessionManager @@ -354,8 +355,9 @@ public function setCustomerGroupId($id) } /** - * Get customer group id - * If customer is not logged in system, 'not logged in' group id will be returned + * Get customer group id. + * + * If customer is not logged in system, 'not logged in' group id will be returned. * * @return int */ @@ -407,24 +409,29 @@ public function checkCustomerId($customerId) } /** + * Sets customer as logged in. + * * @param Customer $customer * @return $this */ public function setCustomerAsLoggedIn($customer) { + $this->regenerateId(); $this->setCustomer($customer); $this->_eventManager->dispatch('customer_login', ['customer' => $customer]); $this->_eventManager->dispatch('customer_data_object_login', ['customer' => $this->getCustomerDataObject()]); - $this->regenerateId(); return $this; } /** + * Sets customer as logged in. + * * @param CustomerData $customer * @return $this */ public function setCustomerDataAsLoggedIn($customer) { + $this->regenerateId(); $this->_httpContext->setValue(Context::CONTEXT_AUTH, true, false); $this->setCustomerData($customer); @@ -567,6 +574,8 @@ public function regenerateId() } /** + * Creates \Magento\Framework\UrlInterface object. + * * @return \Magento\Framework\UrlInterface */ protected function _createUrl() diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php index 4f129f05aa82c..17394c4d94129 100644 --- a/app/code/Magento/Customer/Model/Visitor.php +++ b/app/code/Magento/Customer/Model/Visitor.php @@ -169,6 +169,11 @@ public function initByRequest($observer) $this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)); + // prevent saving Visitor for safe methods, e.g. GET request + if ($this->requestSafety->isSafeMethod()) { + return $this; + } + if (!$this->getId()) { $this->setSessionId($this->session->getSessionId()); $this->save(); diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCustomerFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCustomerFormActionGroup.xml new file mode 100644 index 0000000000000..3112f2b3efee0 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnCustomerFormActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertCustomerGroupOnCustomerForm"> + <arguments> + <argument name="customerGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminNewCustomerPage.url}}" stepKey="amOnCustomerCreatePage"/> + <waitForElementVisible selector="{{AdminCustomerAccountInformationSection.group}}" stepKey="waitForElementVisible"/> + <see selector="{{AdminCustomerAccountInformationSection.group}}" userInput="{{customerGroupName}}" stepKey="assertCustomerGroupPresent"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnProductFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnProductFormActionGroup.xml new file mode 100644 index 0000000000000..0f6d6a5fcfd98 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupOnProductFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertCustomerGroupOnProductForm"> + <arguments> + <argument name="customerGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="amOnProductCreatePage"/> + <waitForElementVisible selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="waitForAdvancedPricingLinkVisible"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForAddButtonVisible"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickAddButton"/> + <see selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{customerGroupName}}" stepKey="assertCustomerGroupPresent"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupPresentInGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupPresentInGridActionGroup.xml new file mode 100644 index 0000000000000..248c93a16def8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerGroupPresentInGridActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertCustomerGroupPresentInGrid" extends="AdminFilterCustomerGroupByNameActionGroup"> + <!--- Assume we are on admin customer group page. --> + <see selector="{{AdminDataGridTableSection.column('Group')}}" userInput="{{customerGroupName}}" after="clickApplyFiltersButton" stepKey="seeCustomerGroupNameInGrid"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertErrorMessageCustomerGroupAlreadyExistsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertErrorMessageCustomerGroupAlreadyExistsActionGroup.xml new file mode 100644 index 0000000000000..5eb52630d906b --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertErrorMessageCustomerGroupAlreadyExistsActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertErrorMessageCustomerGroupAlreadyExists" extends="AdminCreateCustomerGroupActionGroup"> + <remove keyForRemoval="seeCustomerGroupSaveMessage"/> + <waitForElementVisible selector="{{AdminMessagesSection.errorMessage}}" stepKey="waitForElementVisible"/> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput="Customer Group already exists." stepKey="seeErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml new file mode 100644 index 0000000000000..ff6512ba5fc0f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateCustomerWithDefaultAddressWithoutPhoneActionGroup"> + <arguments> + <argument name="customer" defaultValue="Simple_US_Customer"/> + <argument name="address" defaultValue="US_Address_NY"/> + </arguments> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersPage"/> + <click selector="{{AdminCustomerGridMainActionsSection.addNewCustomer}}" stepKey="addNewCustomer"/> + <fillField selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="{{customer.firstname}}" stepKey="fillFirstName"/> + <fillField selector="{{AdminCustomerAccountInformationSection.lastName}}" userInput="{{customer.lastname}}" stepKey="fillLastName"/> + <fillField selector="{{AdminCustomerAccountInformationSection.email}}" userInput="{{customer.email}}" stepKey="fillEmail"/> + <click selector="{{AdminMainActionsSection.saveAndContinue}}" stepKey="clickSaveAndContinue"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the customer." stepKey="seeSuccessMessage"/> + <click selector="{{AdminCustomerAccountInformationSection.addressesButton}}" stepKey="goToAddresses"/> + <waitForPageLoad stepKey="waitForAddresses"/> + <click selector="{{AdminCustomerAddressesSection.addNewAddress}}" stepKey="clickOnAddNewAddress"/> + <waitForPageLoad stepKey="waitForAddressFieldsLoad"/> + <click selector="{{AdminCustomerAddressesSection.defaultBillingAddress}}" stepKey="defaultBillingAddressSetYes"/> + <click selector="{{AdminCustomerAddressesSection.defaultShippingAddress}}" stepKey="defaultShippingAddressSetYes"/> + <fillField selector="{{AdminCustomerAddressesSection.firstNameForAddress}}" userInput="{{address.firstname}}" stepKey="fillFirstNameForAddress"/> + <fillField selector="{{AdminCustomerAddressesSection.lastNameForAddress}}" userInput="{{address.lastname}}" stepKey="fillLastNameForAddress"/> + <fillField selector="{{AdminCustomerAddressesSection.streetAddress}}" userInput="{{address.street[0]}}" stepKey="fillStreetAddress"/> + <fillField selector="{{AdminCustomerAddressesSection.city}}" userInput="{{address.city}}" stepKey="fillCity"/> + <selectOption selector="{{AdminCustomerAddressesSection.country}}" userInput="{{address.country}}" stepKey="selectCountry"/> + <selectOption selector="{{AdminCustomerAddressesSection.state}}" userInput="{{address.state}}" stepKey="selectState"/> + <fillField selector="{{AdminCustomerAddressesSection.zip}}" userInput="{{address.postcode}}" stepKey="fillZip"/> + <click selector="{{AdminSlideOutDialogSection.saveButton}}" stepKey="saveAddress"/> + <dontSee selector="{{AdminCustomerAddressesSection.phoneNumberRequiredMessage}}" stepKey="dontSeePhoneErrorMessage"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="save"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessfullyCreatedMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the customer." stepKey="seeSuccessMessageAgain"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml index 37149e23dc87e..02366415d9edb 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml @@ -46,15 +46,13 @@ <actionGroup name="AdminCreateCustomerWithWebSiteAndGroup"> <arguments> <argument name="customerData" defaultValue="Simple_US_Customer"/> - <argument name="website" type="string" defaultValue="customWebsite"/> - <argument name="storeView" type="string" defaultValue="customStore"/> + <argument name="website" type="string" defaultValue="{{_defaultWebsite.name}}"/> + <argument name="storeView" type="string" defaultValue="{{_defaultStore.name}}"/> </arguments> <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersPage"/> <click stepKey="addNewCustomer" selector="{{AdminCustomerGridMainActionsSection.addNewCustomer}}"/> <selectOption stepKey="selectWebSite" selector="{{AdminCustomerAccountInformationSection.associateToWebsite}}" userInput="{{website}}"/> - <click selector="{{AdminCustomerAccountInformationSection.group}}" stepKey="ClickToExpandGroup"/> - <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceGroupOrCatalogOption('Default (General)')}}" stepKey="waitForCustomerGroupExpand"/> - <click selector="{{AdminCustomerAccountInformationSection.groupValue('Default (General)')}}" after="waitForCustomerGroupExpand" stepKey="ClickToSelectGroup"/> + <selectOption selector="{{AdminCustomerAccountInformationSection.group}}" userInput="{{customerData.group}}" stepKey="selectCustomerGroup"/> <fillField stepKey="FillFirstName" selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="{{customerData.firstname}}"/> <fillField stepKey="FillLastName" selector="{{AdminCustomerAccountInformationSection.lastName}}" userInput="{{customerData.lastname}}"/> <fillField stepKey="FillEmail" selector="{{AdminCustomerAccountInformationSection.email}}" userInput="{{customerData.email}}"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAddressActionGroup.xml new file mode 100644 index 0000000000000..e47aa8809f080 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAddressActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSaveCustomerAddressActionGroup"> + <click selector="{{StorefrontCustomerAddressFormSection.saveAddress}}" stepKey="saveCustomerAddress"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the address." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml index d9da950fe7115..d2d4d86d7f964 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml @@ -12,6 +12,7 @@ <arguments> <argument name="customerFullName" type="string" /> </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> <see userInput="Welcome, {{customerFullName}}!" selector="{{StorefrontPanelHeaderSection.welcomeMessage}}" stepKey="verifyMessage" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml new file mode 100644 index 0000000000000..94cd759e5cef5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerSavedCardActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontCustomerSavedCardActionGroup"> + <arguments> + <argument name="card" type="entity" defaultValue="StoredPaymentMethods"/> + </arguments> + <see selector="{{StorefrontCustomerStoredPaymentMethodsSection.cardNumber}}" userInput="{{card.cardNumberEnding}}" stepKey="verifyCardNumber"/> + <see selector="{{StorefrontCustomerStoredPaymentMethodsSection.expirationDate}}" userInput="{{card.cardExpire}}" stepKey="verifyCardExpire"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml new file mode 100644 index 0000000000000..56e9cfa2c0ab7 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillCustomerSignInPopupFormActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FillCustomerSignInPopupFormActionGroup" > + <arguments> + <argument name="customer" type="entity"/> + </arguments> + <waitForElementVisible selector="{{StorefrontCustomerSignInPopupFormSection.email}}" stepKey="waitEmailFieldVisible"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.email}}" userInput="{{customer.email}}" stepKey="fillCustomerEmail"/> + <fillField selector="{{StorefrontCustomerSignInPopupFormSection.password}}" userInput="{{customer.password}}" stepKey="fillCustomerPassword"/> + <click selector="{{StorefrontCustomerSignInPopupFormSection.signIn}}" stepKey="clickSignIn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillNewCustomerAddressRequiredFieldsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillNewCustomerAddressRequiredFieldsActionGroup.xml new file mode 100644 index 0000000000000..c533d1a6e55bb --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/FillNewCustomerAddressRequiredFieldsActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FillNewCustomerAddressRequiredFieldsActionGroup"> + <arguments> + <argument name="address" type="entity"/> + </arguments> + <fillField selector="{{StorefrontCustomerAddressFormSection.firstName}}" userInput="{{address.firstname}}" stepKey="fillFirstName"/> + <fillField selector="{{StorefrontCustomerAddressFormSection.lastName}}" userInput="{{address.lastname}}" stepKey="fillLastName"/> + <fillField selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}" userInput="{{address.telephone}}" stepKey="fillPhoneNumber"/> + <fillField selector="{{StorefrontCustomerAddressFormSection.streetAddress}}" userInput="{{address.street[0]}}" stepKey="fillStreetAddress"/> + <fillField selector="{{StorefrontCustomerAddressFormSection.city}}" userInput="{{address.city}}" stepKey="fillCity"/> + <selectOption selector="{{StorefrontCustomerAddressFormSection.state}}" userInput="{{address.state}}" stepKey="selectState"/> + <fillField selector="{{StorefrontCustomerAddressFormSection.zip}}" userInput="{{address.postcode}}" stepKey="fillZip"/> + <selectOption selector="{{StorefrontCustomerAddressFormSection.country}}" userInput="{{address.country}}" stepKey="selectCountry"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml new file mode 100644 index 0000000000000..9975431e9fe6e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenStorefrontStoredPaymentMethodsPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="OpenStorefrontCustomerStoredPaymentMethodsPageActionGroup"> + <amOnPage url="{{StorefrontCustomerStoredPaymentMethodsPage.url}}" stepKey="goToCustomerStoredPaymentMethodsPage"/> + <waitForPageLoad stepKey="waitForCustomerStoredPaymentMethodsPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index ef956293d367b..da01887133997 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -41,6 +41,18 @@ <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}"/> <waitForPageLoad stepKey="waitForCreateAccountButtonToLoad" /> </actionGroup> + + <actionGroup name="StorefrontFillRegistrationFormActionGroup" extends="StorefrontCreateCustomerSignedUpNewsletterActionGroup"> + <remove keyForRemoval="checkSignUpForNewsletter"/> + <remove keyForRemoval="clickCreateAccountButton"/> + <remove keyForRemoval="waitForCreateAccountButtonToLoad"/> + </actionGroup> + + <actionGroup name="SaveRegistrationFormActionGroup"> + <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}"/> + <waitForPageLoad stepKey="waitForCreateAccountButtonToLoad" /> + </actionGroup> + <actionGroup name="AssertSignedUpNewsletterActionGroup"> <arguments> <argument name="customer" defaultValue="CustomerEntityOne"/> @@ -57,7 +69,7 @@ <arguments> <argument name="Address"/> </arguments> - + <amOnPage url="customer/address/new/" stepKey="goToAddressPage"/> <waitForPageLoad stepKey="waitForAddressPage"/> <fillField stepKey="fillFirstName" selector="{{StorefrontCustomerAddressSection.firstName}}" userInput="{{Address.firstname}}"/> @@ -154,4 +166,4 @@ <waitForPageLoad stepKey="waitForRegistered" after="clickCreateAccountButton"/> <remove keyForRemoval="seeThankYouMessage"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertRegistrationPageFieldsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertRegistrationPageFieldsActionGroup.xml new file mode 100644 index 0000000000000..d76277d2e5e45 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertRegistrationPageFieldsActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertRegistrationPageFields"> + <seeInCurrentUrl url="{{StorefrontCustomerCreatePage.url}}" stepKey="seeCreateNewCustomerAccountPage"/> + <seeElement selector="{{StorefrontCustomerCreateFormSection.firstnameField}}" stepKey="seeFirstNameField"/> + <seeElement selector="{{StorefrontCustomerCreateFormSection.lastnameField}}" stepKey="seeFLastNameField"/> + <seeElement selector="{{StorefrontCustomerCreateFormSection.emailField}}" stepKey="seeEmailField"/> + <seeElement selector="{{StorefrontCustomerCreateFormSection.passwordField}}" stepKey="seePasswordField"/> + <seeElement selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}" stepKey="seeConfirmPasswordField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml index de97bb47de796..295346875e3cc 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontCustomerLogoutActionGroup"> <amOnPage url="{{StorefrontCustomerLogoutPage.url}}" stepKey="storefrontSignOut"/> + <waitForPageLoad stepKey="waitForSignOut"/> </actionGroup> <actionGroup name="StorefrontSignOutActionGroup"> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml new file mode 100644 index 0000000000000..accf8f40bb282 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillBillingAddressActionGroup"> + <arguments> + <argument name="address" defaultValue="CustomerUKAddress" type="entity"/> + </arguments> + <fillField selector="{{CheckoutPaymentSection.guestFirstName}}" userInput="{{address.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutPaymentSection.guestLastName}}" userInput="{{address.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutPaymentSection.guestStreet}}" userInput="{{address.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutPaymentSection.guestCity}}" userInput="{{address.city}}" stepKey="enterCity"/> + <fillField selector="{{CheckoutPaymentSection.guestPostcode}}" userInput="{{address.postcode}}" stepKey="enterPostcode"/> + <selectOption selector="{{CheckoutPaymentSection.guestCountry}}" userInput="{{address.country_id}}" stepKey="enterCountry"/> + <fillField selector="{{CheckoutPaymentSection.guestTelephone}}" userInput="{{address.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml new file mode 100644 index 0000000000000..16d7fd197b52f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" extends="StorefrontFillCustomerLoginFormActionGroup"> + <remove keyForRemoval="fillPassword"/> + <fillField userInput="{{customer.password}}_INCORRECT" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml new file mode 100644 index 0000000000000..57c350700d57d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenMyAccountPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenMyAccountPageActionGroup"> + <amOnPage url="{{StorefrontCustomerDashboardPage.url}}" stepKey="goToMyAccountPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml new file mode 100644 index 0000000000000..5e24592bf017f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontRegisterCustomerFromOrderSuccessPage"> + <arguments> + <argument name="customer" /> + </arguments> + <click selector="{{CheckoutSuccessRegisterSection.createAccountButton}}" stepKey="clickCreateAccountButton"/> + <fillField selector="{{StorefrontCustomerCreateFormSection.passwordField}}" userInput="{{customer.password}}" stepKey="typePassword"/> + <fillField selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}" userInput="{{customer.password}}" stepKey="typeConfirmationPassword"/> + <click selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" stepKey="clickOnCreateAccount"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="Thank you for registering" stepKey="verifyAccountCreated"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml index 4c59edbcb8057..85e23940e1409 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml @@ -16,12 +16,4 @@ <see userInput="You have logged out." stepKey="seeSuccessMessage" /> <waitForElementVisible selector="//*[@data-ui-id='messages-message-success']" stepKey="waitForSuccessMessageLoggedOut" time="5"/> </actionGroup> - - <!--Login New User--> - <actionGroup name="LoginNewUser"> - <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> - <fillField userInput="{{NewAdmin.username}}" selector="{{LoginFormSection.username}}" stepKey="fillUsername"/> - <fillField userInput="{{NewAdmin.password}}" selector="{{LoginFormSection.password}}" stepKey="fillPassword"/> - <click selector="{{LoginFormSection.signIn}}" stepKey="clickLogin"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index 5d1a900167144..4d7a39b3246e1 100755 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -271,4 +271,45 @@ <data key="default_shipping">Yes</data> <requiredEntity type="region">RegionUT</requiredEntity> </entity> + <entity name="CustomerUKAddress" type="address"> + <data key="firstname">Jane</data> + <data key="lastname">Miller</data> + <data key="company">Magento</data> + <data key="telephone">44 20 7123 1234</data> + <array key="street"> + <item>1 London Bridge Street</item> + </array> + <data key="country_id">GB</data> + <data key="country">United Kingdom</data> + <data key="city">London</data> + <data key="state"></data> + <data key="postcode">SE12 9GF</data> + </entity> + <entity name="DE_Address_Berlin_Not_Default_Address" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>Augsburger Strabe 41</item> + </array> + <data key="city">Berlin</data> + <data key="country_id">DE</data> + <data key="postcode">10789</data> + <data key="telephone">333-33-333-33</data> + <data key="country">Germany</data> + </entity> + <entity name="US_Address_California"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>6161 West Centinela Avenue</item> + </array> + <data key="city">Culver City</data> + <data key="country_id">United States</data> + <data key="country">United States</data> + <data key="state">California</data> + <data key="postcode">90230</data> + <data key="telephone">555-55-555-55</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml index 11a47459ab7b3..ab4307082595d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml @@ -14,18 +14,41 @@ <entity name="CustomerAccountSharingPerWebsite" type="account_share_scope_value"> <data key="value">1</data> </entity> - <entity name="CustomerAccountSharingGlobal" type="customer_account_sharing_config"> <requiredEntity type="account_share_scope_value">GlobalCustomerAccountSharing</requiredEntity> </entity> <entity name="GlobalCustomerAccountSharing" type="account_share_scope_value"> <data key="value">0</data> </entity> - + <entity name="EnableCustomerRedirectToDashboardConfigData"> + <data key="path">customer/startup/redirect_dashboard</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableCustomerRedirectToDashboardConfigData"> + <data key="path">customer/startup/redirect_dashboard</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> <entity name="CustomerAccountSharingSystemValue" type="customer_account_sharing_config_inherit"> <requiredEntity type="account_share_scope_inherit">CustomerAccountSharingInherit</requiredEntity> </entity> <entity name="CustomerAccountSharingInherit" type="account_share_scope_inherit"> <data key="inherit">true</data> </entity> + <entity name="StorefrontCustomerLockoutFailuresDefaultConfigData"> + <!-- Magento default value --> + <data key="path">customer/password/lockout_failures</data> + <data key="scope_id">0</data> + <data key="label">10</data> + <data key="value">10</data> + </entity> + <entity name="StorefrontCustomerLockoutFailures5ConfigData"> + <data key="path">customer/password/lockout_failures</data> + <data key="scope_id">0</data> + <data key="label">5</data> + <data key="value">5</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index f8943dcfe8369..c7a73b61dc48a 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -44,8 +44,22 @@ <data key="password">pwdTest123!</data> <data key="store_id">0</data> <data key="website_id">0</data> + <data key="group">General</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="SimpleUsCustomerWithNewCustomerGroup" type="customer"> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_TX</requiredEntity> + <var entityType="customerGroup" entityKey="id" key="group_id" /> + </entity> <entity name="UsCustomerAssignedToNewCustomerGroup" type="customer"> <var key="group_id" entityKey="id" entityType="customerGroup" /> <data key="default_billing">true</data> @@ -216,6 +230,48 @@ <data key="store_id">0</data> <data key="website_id">0</data> </entity> + <entity name="UKCustomer" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">david@email.com</data> + <data key="firstname">David</data> + <data key="lastname">Mill</data> + <data key="fullname">David Mill</data> + <data key="password">pwdTest123!</data> + <data key="gender">0</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">updateCustomerUKAddress</requiredEntity> + </entity> + <entity name="Customer_US_UK_DE" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_NY</requiredEntity> + <requiredEntity type="address">DE_Address_Berlin_Not_Default_Address</requiredEntity> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + </entity> + <entity name="Retailer_Customer" type="customer"> + <data key="group_id">3</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_CA</requiredEntity> + </entity> <entity name="Simple_US_Customer_Two_Addresses" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> @@ -230,4 +286,27 @@ <requiredEntity type="address">US_Address_TX</requiredEntity> <requiredEntity type="address">US_Address_NY_Not_Default_Address</requiredEntity> </entity> + <entity name="Simple_US_Customer_Incorrect_Email" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email">><script>alert(1);</script>@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_CA</requiredEntity> + </entity> + <entity name="John_Smith_Customer" type="customer"> + <data key="group_id">1</data> + <data key="email" unique="prefix">john.smith@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Smith</data> + <data key="fullname">John Smith</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml index eaca1c820e49e..83a4d0a4ba82e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml @@ -10,6 +10,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerDashboardPage" url="/customer/account/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerDashboardAccountInformationSection" /> + <section name="StorefrontCustomerResentOrdersSection"/> <section name="StorefrontCustomerSidebarSection"/> + <section name="StorefrontMinicartSection"/> + <section name="StorefrontCustomerFooterSection"/> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml new file mode 100644 index 0000000000000..bec802689a6cc --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerStoredPaymentMethodsPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerStoredPaymentMethodsPage" url="/vault/cards/listaction/" area="storefront" module="Magento_Customer"> + <section name="StorefrontCustomerStoredPaymentMethodsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index 71e3e673477d2..4b36486f0bd17 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -31,5 +31,9 @@ <element name="firstNameRequiredMessage" type="text" selector="//input[@name='customer[firstname]']/../label[contains(.,'This is a required field.')]"/> <element name="lastNameRequiredMessage" type="text" selector="//input[@name='customer[lastname]']/../label[contains(.,'This is a required field.')]"/> <element name="emailRequiredMessage" type="text" selector="//input[@name='customer[email]']/../label[contains(.,'This is a required field.')]"/> + <element name="customAttribute" type="select" selector="//select[contains(@name, 'customer[{{attribute_code}}]')]" parameterized="true"/> + <element name="disabledGroup" type="text" selector="//div[@class='admin__action-group-wrap admin__action-multiselect-wrap action-select-wrap _disabled']"/> + <element name="customerAttribute" type="input" selector="//input[contains(@name,'{{attributeCode}}')]" parameterized="true"/> + <element name="attributeImage" type="block" selector="//div[contains(concat(' ',normalize-space(@class),' '),' file-uploader-preview ')]//img"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesComparisonListSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesComparisonListSection.xml new file mode 100644 index 0000000000000..46eed69f22467 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesComparisonListSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerActivitiesComparisonListSection"> + <element name="addProductToOrder" type="text" selector="//div[@id='order-sidebar_compared']//tr[td[.='{{productName}}']]//input[contains(@name,'add')]" parameterized="true" timeout="30"/> + <element name="addToOrderConfigure" type="button" selector="//div[@id='order-sidebar_compared']//tr[td[contains(.,'{{productName}}')]]//a[contains(@class, 'icon-configure')]" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesConfigureSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesConfigureSection.xml new file mode 100644 index 0000000000000..cbe22fd26e402 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesConfigureSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerActivitiesConfigureSection"> + <element name="addAttribute" type="select" selector="//select[contains(concat(' ',normalize-space(@class),' '),' super-attribute-select ')]" timeout="30"/> + <element name="dropdownProductSelection" type="select" selector="//option[contains(text(), '{{productName}}')]" parameterized="true" timeout="30"/> + <element name="okButton" type="button" selector="//button[contains(concat(' ',normalize-space(@class),' '),' action-primary ')]" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesLastOrderedSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesLastOrderedSection.xml new file mode 100644 index 0000000000000..22fa1b5bd21cb --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesLastOrderedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerActivitiesLastOrderedSection"> + <element name="addProductToOrder" type="text" selector="//div[@id='sidebar_data_reorder']//tr[td[.='{{productName}}']]//input[contains(@name,'add')]" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesRecentlyViewedSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesRecentlyViewedSection.xml new file mode 100644 index 0000000000000..b3a0151135491 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesRecentlyViewedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerActivitiesRecentlyViewedSection"> + <element name="addToOrderConfigure" type="button" selector="//div[@id='sidebar_data_pviewed']//tr[td[contains(.,'{{productName}}')]]//a[contains(@class, 'icon-configure')]" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesShoppingCartSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesShoppingCartSection.xml new file mode 100644 index 0000000000000..fb8d05f4274cc --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerActivitiesShoppingCartSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerActivitiesShoppingCartSection"> + <element name="productName" type="text" selector="//div[@id='sidebar_data_cart']//td[@class='col-item']"/> + <element name="productPrice" type="text" selector="//div[@id='sidebar_data_cart']//td[@class='col-price']"/> + <element name="addToOrder" type="checkbox" selector="//input[contains(@id, 'sidebar-add_cart_item')]"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml index e743c4af66d9f..3ecbf5ff450c8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml @@ -17,5 +17,6 @@ <element name="filters" type="button" selector="button[data-action='grid-filter-expand']" timeout="30"/> <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']" timeout="30"/> <element name="headerRow" type="text" selector=".admin__data-grid-header-row.row.row-gutter"/> + <element name="clearFilter" type="button" selector=".admin__data-grid-header .admin__data-grid-filters-current button.action-clear" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml new file mode 100644 index 0000000000000..a01687990999e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerCreateNewOrderSection"> + <element name="updateChangesBtn" type="button" selector=".order-sidebar .actions .action-default.scalable" timeout="30"/> + <element name="productName" type="text" selector="#order-items_grid span[id*=order_item]"/> + <element name="productPrice" type="text" selector=".even td[class=col-price] span[class=price]"/> + <element name="productQty" type="input" selector="td[class=col-qty] input"/> + <element name="gridCell" type="text" selector="//div[contains(@id, 'order-items_grid')]//tbody[{{row}}]//td[count(//table[contains(@class, 'order-tables')]//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml index 304068d89b729..18bc26bfd4987 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml @@ -13,5 +13,6 @@ <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> <element name="resetPassword" type="button" selector="#resetPassword" timeout="30"/> <element name="manageShoppingCart" type="button" selector="#manage_quote" timeout="30"/> + <element name="createOrderBtn" type="button" selector="#order" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml index 89fed43184b84..fb3d1570848e4 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditCustomerOrdersSection"> <element name="orderGrid" type="text" selector="#customer_orders_grid_table"/> + <element name="orderIdInGrid" type="text" selector="//td[contains(., '{{orderId}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml index 8a633ec5bc271..9828c42211e41 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -20,5 +20,8 @@ <element name="confirmNewPassword" type="input" selector="#password-confirmation"/> <element name="confirmNewPasswordError" type="text" selector="#password-confirmation-error"/> <element name="email" type="input" selector=".form-edit-account input[name='email']" /> + <element name="emailErrorMessage" type="text" selector="#email-error"/> + <element name="currentPasswordErrorMessage" type="text" selector="#current-password-error"/> + <element name="emailField" type="input" selector="#email"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml index c00b54b3964da..a501a7ac4a618 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontCustomerAccountMainSection"> <element name="pageTitle" type="text" selector="#maincontent .column.main [data-ui-id='page-title-wrapper']" /> <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true" /> + <element name="alertMessage" type="text" selector=".page.messages [role=alert]"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml index aad9d02842271..dee62070ab9bc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml @@ -10,13 +10,17 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerAddressesSection"> <element name="defaultBillingAddress" type="text" selector=".box-address-billing" /> + <element name="billingAddressBlock" type="text" selector=".box-address-billing address" /> <element name="editDefaultBillingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Billing Address']" timeout="30"/> <element name="defaultShippingAddress" type="text" selector=".box-address-shipping" /> + <element name="shippingAddressBlock" type="text" selector=".box-address-shipping address" /> <element name="editDefaultShippingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Shipping Address']" timeout="30"/> <element name="addressesList" type="text" selector=".additional-addresses" /> <element name="deleteAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action delete']" parameterized="true"/> <element name="editAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action edit']" parameterized="true" timeout="30"/> <element name="addNewAddress" type="button" selector="//span[text()='Add New Address']"/> <element name="numberOfAddresses" type="text" selector=".toolbar-number"/> + <element name="shippingAddress" type="text" selector="//div[contains(@class,'box box-shipping-address')]//div/address"/> + <element name="billingAddress" type="text" selector="//div[contains(@class,'box box-billing-address')]//div/address"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCompareProductSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCompareProductSection.xml new file mode 100644 index 0000000000000..c2d0d58b3c28d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCompareProductSection.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerCompareProductSection"> + <element name="title" type="text" selector="#block-compare-heading"/> + <element name="emptyMessage" type="text" selector=".block-compare .empty"/> + + <element name="productListMainArea" type="block" selector="#compare-items"/> + <element name="productCount" type="text" selector=".block-compare .counter"/> + + <element name="productByName" type="button" selector="//*[contains(@class, 'product-items')]//a[contains(@class, 'product-item-link')][contains(text(), '{{productName}}')]" parameterized="true"/> + <element name="removeProductByName" type="button" selector="//li[contains(@class, 'product-item')]//*[contains(text(), '{{productName}}')]/../../a" parameterized="true"/> + + <element name="compare" type="button" selector=".actions-toolbar .compare" timeout="30"/> + <element name="clearAll" type="button" selector="#compare-clear-all" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml index 8881a2a012ce8..ba1f10a480745 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -30,5 +30,6 @@ <element name="yesNoAttribute" type="select" selector="//select[@id='{{var}}']" parameterized="true" /> <element name="yesNoOptionAttribute" type="select" selector="//select[@id='{{var}}']/option[2]" parameterized="true" /> <element name="selectedOption" type="text" selector="//select[@id='{{var}}']/option[@selected='selected']" parameterized="true"/> + <element name="attributeLabel" type="text" selector="//span[text()='{{attributeLabel}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml index 93e7bf71b0894..85d0fd166a77e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml @@ -12,6 +12,7 @@ <element name="ContactInformation" type="textarea" selector=".box.box-information .box-content"/> <element name="edit" type="block" selector=".action.edit" timeout="15"/> <element name="changePassword" type="block" selector=".action.change-password" timeout="15"/> + <element name="editLink" type="text" selector=".box-information .edit"/> </section> <section name="StorefrontCustomerAddressSection"> <element name="firstName" type="input" selector="#firstname"/> @@ -27,4 +28,7 @@ <element name="country" type="select" selector="#country"/> <element name="saveAddress" type="button" selector="[data-action='save-address']" timeout="30"/> </section> + <section name="StorefrontCustomerRecentOrdersSection"> + <element name="orderTotal" type="text" selector=".total .price"/> + </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerFooterSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerFooterSection.xml new file mode 100644 index 0000000000000..f68a69df0584c --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerFooterSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerFooterSection"> + <element name="footerBlock" type="block" selector="//footer"/> + <element name="formSubscribe" type="input" selector="input#newsletter"/> + <element name="buttonSubscribe" type="button" selector="//form[@id='newsletter-validate-detail']//button[contains(@class, 'subscribe')]" timeout="15"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index e8b11b27ddc70..ec5141d84b1bd 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml @@ -13,5 +13,9 @@ <element name="productCustomOptions" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[normalize-space(.)='{{var3}}']" parameterized="true"/> <element name="productCustomOptionsFile" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[contains(.,'{{var3}}')]" parameterized="true"/> <element name="productCustomOptionsLink" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd//a[text() = '{{var3}}']" parameterized="true"/> + <element name="status" type="text" selector="//td[contains(concat(' ',normalize-space(@class),' '),' col status ')]"/> + <element name="viewOrder" type="button" selector="//td[contains(concat(' ',normalize-space(@class),' '),' col actions ')]/a[contains(concat(' ',normalize-space(@class),' '),' action view ')]"/> + <element name="tabRefund" type="button" selector="//a[text()='Refunds']"/> + <element name="grandTotalRefund" type="text" selector="td[data-th='Grand Total'] > strong > span.price"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml index f831aabddd4ee..09b79fe831188 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml @@ -16,5 +16,7 @@ <element name="printOrderLink" type="text" selector="a.action.print" timeout="30"/> <element name="shippingAddress" type="text" selector=".box.box-order-shipping-address"/> <element name="billingAddress" type="text" selector=".box.box-order-billing-address"/> + <element name="orderStatusInGrid" type="text" selector="//td[contains(.,'{{orderId}}')]/../td[contains(.,'{{status}}')]" parameterized="true"/> + <element name="pager" type="block" selector=".pager"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerResentOrdersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerResentOrdersSection.xml new file mode 100644 index 0000000000000..6dc81a3a9339d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerResentOrdersSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerResentOrdersSection"> + <element name="blockResentOrders" type="text" selector="//div[@class='block-title order']"/> + <element name="viewOrder" type="button" selector="//td[text()='{{orderId}}']/following-sibling::td[@data-th='Actions']/a[@class='action view']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml index 407c6480e9dde..c6b9aa0372edc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerSidebarSection"> - <element name="sidebarTab" type="text" selector="//div[@id='block-collapsible-nav']//a[text()='{{tabName}}']" parameterized="true"/> + <element name="sidebarTab" type="text" selector="//div[@id='block-collapsible-nav']//a[text()='{{tabName}}']" parameterized="true" timeout="60"/> <element name="sidebarCurrentTab" type="text" selector="//div[@id='block-collapsible-nav']//*[contains(text(), '{{var}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index 7bc057b8be7b7..6b333a0c714c0 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -13,11 +13,19 @@ <element name="passwordField" type="input" selector="#pass"/> <element name="signInAccountButton" type="button" selector="#send2" timeout="30"/> <element name="forgotPasswordLink" type="button" selector=".action.remind" timeout="10"/> + <element name="customerLoginBlock" type="text" selector=".login-container .block.block-customer-login"/> </section> <section name="StorefrontCustomerSignInPopupFormSection"> <element name="errorMessage" type="input" selector="[data-ui-id='checkout-cart-validationmessages-message-error']"/> <element name="email" type="input" selector="#customer-email"/> <element name="password" type="input" selector="#pass"/> <element name="signIn" type="button" selector="#send2" timeout="30"/> + <element name="createAnAccount" type="button" selector="//div[contains(@class,'actions-toolbar')]//a[contains(.,'Create an Account')]" timeout="30"/> + </section> + <section name="StorefrontCustomerSignInLinkSection"> + <element name="signInLink" type="button" selector=".action-auth-toggle" timeout="30"/> + <element name="email" type="input" selector="#login-email"/> + <element name="password" type="input" selector="#login-password"/> + <element name="signInBtn" type="button" selector="//button[contains(@class, 'action-login') and not(contains(@id,'send2'))]" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml new file mode 100644 index 0000000000000..d6b586e42f28c --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerStoredPaymentMethodsSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerStoredPaymentMethodsSection"> + <element name="cardNumber" type="text" selector="td.card-number"/> + <element name="expirationDate" type="text" selector="td.card-expire"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerGroupAlreadyExistsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerGroupAlreadyExistsTest.xml new file mode 100644 index 0000000000000..6b1c1f29f97fc --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerGroupAlreadyExistsTest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateCustomerGroupAlreadyExistsTest"> + <annotations> + <features value="Create customer group already exists"/> + <stories value="Create customer group"/> + <title value="Create customer group already exists"/> + <description value="Create customer group already exists"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-5302"/> + <group value="customer"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Steps: 1. Log in to backend as admin user. + 2. Navigate to Stores > Other Settings > Customer Groups. + 3. Start to create new Customer Group. + 4. Fill in all data according to data set: Group Name "General", Tax Class "Retail customer" + 5. Click "Save Customer Group" button. --> + <!-- Assert "Customer Group already exists." error message displayed --> + <actionGroup ref="AdminAssertErrorMessageCustomerGroupAlreadyExists" stepKey="seeErrorMessageCustomerGroupAlreadyExists"> + <argument name="groupName" value="{{GeneralCustomerGroup.code}}"/> + <argument name="taxClass" value="{{GeneralCustomerGroup.tax_class_name}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml index 43f2aa7f8de95..a487571c43534 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml @@ -85,6 +85,7 @@ <see selector="{{AdminCustomerAddressesDefaultBillingSection.addressDetails}}" userInput="{{US_Address_CA.telephone}}" stepKey="seePhoneNumberInDefaultAddressSection"/> <!--Assert Customer Address Grid --> + <conditionalClick selector="{{AdminCustomerAddressesGridActionsSection.clearFilter}}" dependentSelector="{{AdminCustomerAddressesGridActionsSection.clearFilter}}" visible="true" stepKey="clearAddressesGridFilter"/> <see selector="{{AdminCustomerAddressesGridSection.customerAddressGrid}}" userInput="{{US_Address_CA.street}}" stepKey="seeStreetAddress"/> <see selector="{{AdminCustomerAddressesGridSection.customerAddressGrid}}" userInput="{{US_Address_CA.city}}" stepKey="seeCity"/> <see selector="{{AdminCustomerAddressesGridSection.customerAddressGrid}}" userInput="{{US_Address_CA.country}}" stepKey="seeCountry"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateRetailCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateRetailCustomerGroupTest.xml new file mode 100644 index 0000000000000..4f1d88ffe99f5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateRetailCustomerGroupTest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateRetailCustomerGroupTest"> + <annotations> + <features value="Create retail customer group"/> + <stories value="Create customer group"/> + <title value="Create retail customer group"/> + <description value="Create retail customer group"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-5301"/> + <group value="customer"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteCustomerGroupActionGroup" stepKey="deleteCustomerGroup"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Steps: 1. Log in to backend as admin user. + 2. Navigate to Stores > Other Settings > Customer Groups. + 3. Start to create new Customer Group. + 4. Fill in all data according to data set. Tax Class - "Retail customer" + 5. Click "Save Customer Group" button. --> + <!-- Assert "You saved the customer group." success message displayed --> + <!-- Assert created Customer Group displayed In Grid --> + <actionGroup ref="AdminCreateCustomerGroupActionGroup" stepKey="createCustomerGroup"> + <argument name="groupName" value="{{CustomCustomerGroup.code}}"/> + <argument name="taxClass" value="{{CustomCustomerGroup.tax_class_name}}"/> + </actionGroup> + <actionGroup ref="AdminAssertCustomerGroupPresentInGrid" stepKey="assertCustomerGroupDisplayedInGrid"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + + <!-- 6. Go to Catalog -> Products -> click "Add Product" button -> click "Advanced Pricing" link -> Customer Group Price -> click "Add" button --> + <!-- Assert: Customer Group Displayed On Product Form --> + <actionGroup ref="AdminAssertCustomerGroupOnProductForm" stepKey="assertCustomerGroupDisplayedOnProductForm"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + + <!-- 7. Go to Customers -> All Customers -> click "Add New Customer" button --> + <!-- Assert created Customer Group displayed On Customer Form --> + <actionGroup ref="AdminAssertCustomerGroupOnCustomerForm" stepKey="assertCustomerGroupDisplayedOnCustomerForm"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + + <!-- 8. Go to Marketing - Catalog Price Rule - click "Add New Rule" button --> + <!-- Assert created Customer Group displayed On Catalog Price Rule Form --> + <actionGroup ref="AdminAssertCustomerGroupOnCatalogPriceRuleForm" stepKey="assertCustomerGroupDisplayedOnCatalogPriceRuleForm"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + + <!-- 9. Go to Marketing - Cart Price Rule - click "Add New Rule" button --> + <!-- Assert created Customer Group displayed On Cart Price Rule Form --> + <actionGroup ref="AdminAssertCustomerGroupOnCartPriceRuleForm" stepKey="assertCustomerGroupDisplayedOnCartPriceRuleForm"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateTaxClassCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateTaxClassCustomerGroupTest.xml new file mode 100644 index 0000000000000..7d54ede7c1612 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateTaxClassCustomerGroupTest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTaxClassCustomerGroupTest"> + <annotations> + <features value="Create tax class customer group"/> + <stories value="Create customer group"/> + <title value="Create tax class customer group"/> + <description value="Create tax class customer group"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-5303"/> + <group value="customer"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create Tax Class "Customer tax class"--> + <createData entity="customerTaxClass" stepKey="createCustomerTaxClass"/> + <getData entity="customerTaxClass" stepKey="customerTaxClassData"> + <requiredEntity createDataKey="createCustomerTaxClass"/> + </getData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteCustomerGroupActionGroup" stepKey="deleteCustomerGroup"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFilters"/> + <deleteData createDataKey="createCustomerTaxClass" stepKey="deleteCustomerTaxClass"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Steps: 1. Log in to backend as admin user. + 2. Navigate to Stores > Other Settings > Customer Groups. + 3. Start to create new Customer Group. + 4. Fill in all data according to data set: Tax Class "Customer tax class" + 5. Click "Save Customer Group" button. --> + <!-- Assert "You saved the customer group." success message displayed --> + <!-- Assert created Customer Group displayed In Grid --> + <actionGroup ref="AdminCreateCustomerGroupActionGroup" stepKey="createNewCustomerGroup"> + <argument name="groupName" value="{{CustomCustomerGroup.code}}"/> + <argument name="taxClass" value="$$customerTaxClassData.class_name$$"/> + </actionGroup> + <actionGroup ref="AdminAssertCustomerGroupPresentInGrid" stepKey="assertCustomerGroupDisplayedInGrid"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + <!-- 6. Go to Customers -> All Customers -> click "Add New Customer" button --> + <!-- Assert created Customer Group displayed On Customer Form --> + <actionGroup ref="AdminAssertCustomerGroupOnCustomerForm" stepKey="assertCustomerGroupDisplayedOnCustomerForm"> + <argument name="customerGroupName" value="{{CustomCustomerGroup.code}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml new file mode 100644 index 0000000000000..6a7aeab78bcde --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminExactMatchSearchInCustomerGridTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminExactMatchSearchInCustomerGridTest"> + <annotations> + <features value="Customer"/> + <stories value="Customer Search"/> + <title value="Admin customer grid exact match searching"/> + <description value="Admin customer grid exact match searching with quotes in keyword"/> + <severity value="MAJOR"/> + <testCaseId value="MC-16335"/> + <useCaseId value="MAGETWO-99605"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createFirstCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createSecondCustomer"> + <field key="firstname">"Jane Doe"</field> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createFirstCustomer" stepKey="deleteFirstCustomer"/> + <deleteData createDataKey="createSecondCustomer" stepKey="deleteSecondCustomer"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminResetFilterInCustomerAddressGrid" stepKey="clearCustomerGridFilter"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Step 1: Go to Customers > All Customers--> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <!--Step 2: On Customers grid page search customer by keyword with quotes--> + <actionGroup ref="searchAdminDataGridByKeyword" stepKey="searchCustomer"> + <argument name="keyword" value="$$createSecondCustomer.firstname$$"/> + </actionGroup> + <!--Step 3: Check if customer is placed in a first row and clear grid filter--> + <actionGroup ref="AdminAssertCustomerInCustomersGrid" stepKey="checkCustomerInGrid"> + <argument name="text" value="$$createSecondCustomer.fullname$$"/> + <argument name="row" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml new file mode 100644 index 0000000000000..daab5fd2061fb --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressStateContainValuesOnceTest.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminVerifyCustomerAddressStateContainValuesOnceTest"> + <annotations> + <features value="Customer"/> + <stories value="Update Customer Address"/> + <title value="State/Province dropdown contain values once"/> + <description value="When editing a customer in the backend from the Magento Admin Panel the State/Province should only be listed once"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-99461"/> + <useCaseId value="MAGETWO-99302"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="firstCustomer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="firstCustomer" stepKey="deleteFirstCustomer"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteSecondCustomer"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> + <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to Customers > All Customers.--> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + + <!--Select created customer, Click Edit mode--> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPageWithAddresses"> + <argument name="customer" value="$$firstCustomer$$"/> + </actionGroup> + + <!--Select Addresses tab--> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTabOfFirstCustomer"/> + <waitForPageLoad stepKey="waitForAddressesOfFirstCustomer"/> + + <!--Click on Edit link for Default Billing Address--> + <click selector="{{AdminCustomerAddressesDefaultBillingSection.editButton}}" stepKey="clickEditDefaultBillingAddress"/> + <waitForPageLoad stepKey="waitForCustomerAddressAddUpdateFormLoad"/> + + <!--Check that State/Province drop down contain all values once--> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesSection.regionId(US_Address_NY.state)}}" stepKey="seeOnlyOneRegionInSelectStateForFirstCustomer"/> + + <!--Go to Customers > All customers, Click Add new Customers, fill all necessary fields, Save--> + <actionGroup ref="AdminCreateCustomerWithWebSiteAndGroup" stepKey="createSimpleUSCustomerWithoutAddress"/> + + <!--Select new created customer, Click Edit mode--> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerPageWithoutAddresses"> + <argument name="customer" value="Simple_US_Customer"/> + </actionGroup> + + <!--Select Addresses tab, Click on create new addresses btn--> + <click selector="{{AdminEditCustomerInformationSection.addresses}}" stepKey="openAddressesTabOfSecondCustomer"/> + <waitForPageLoad stepKey="waitForAddressesOfSecondCustomer"/> + <click selector="{{AdminCustomerAddressesSection.addNewAddress}}" stepKey="clickAddNewAddressButton"/> + <waitForPageLoad stepKey="waitForAddUpdateCustomerAddressForm"/> + + <!--Select Country = United States and check that State/Province drop down contain all values once--> + <click selector="{{AdminCustomerAddressesSection.country}}" stepKey="clickCountryToOpenListOfCountries"/> + <click selector="{{AdminCustomerAddressesSection.countryId(US_Address_NY.country_id)}}" stepKey="fillCountry"/> + <seeNumberOfElements userInput="1" selector="{{AdminCustomerAddressesSection.regionId(US_Address_NY.state)}}" stepKey="seeOnlyOneRegionInSelectStateForSecondCustomer"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml index f39394ef312e4..6de03e225ae08 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml @@ -9,9 +9,10 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AllowedCountriesRestrictionApplyOnBackendTest"> <annotations> + <features value="Customer"/> + <stories value="Country filter"/> <title value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> <description value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> - <features value="Customer"/> <severity value="MAJOR"/> <testCaseId value="MC-6441"/> <useCaseId value="MAGETWO-91523"/> @@ -113,6 +114,6 @@ <waitForPageLoad stepKey="waitForCustomersGrid"/> <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomersGrid"/> <executeJS function="var len = document.querySelectorAll('{{AdminCustomerFiltersSection.countryOptions}}').length; return len-1;" stepKey="countriesAmount2"/> - <assertEquals expected='($countriesAmount)' expectedType="integer" actual="($countriesAmount2)" stepKey="assertCountryAmounts"/> + <assertEquals expected="($countriesAmount)" actual="($countriesAmount2)" stepKey="assertCountryAmounts"/> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml index fb083f39ad387..e35a1ad61dc7c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml @@ -67,6 +67,9 @@ <stories value="Change Customer Group"/> <group value="customer"/> <group value="mtf_migrated"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <remove keyForRemoval="filterCustomer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml index a085a67167f60..b19966e0102b6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml @@ -10,18 +10,20 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="DeleteCustomerGroupTest"> <annotations> - <title value="Delete customer group entity test"/> - <description value="Delete a customer group"/> + <features value="Customer"/> <stories value="Delete Customer Group"/> - <testCaseId value="MC-14590" /> + <title value="Delete Customer Group in Admin Panel"/> + <description value="Admin should be able to delete a Customer Group"/> + <testCaseId value="MC-14590"/> + <severity value="MAJOR"/> <group value="customers"/> <group value="mtf_migrated"/> </annotations> <before> - <createData entity="CustomCustomerGroup" stepKey="customerGroup" /> + <createData entity="CustomCustomerGroup" stepKey="customerGroup"/> <createData entity="UsCustomerAssignedToNewCustomerGroup" stepKey="customer"> - <requiredEntity createDataKey="customerGroup" /> + <requiredEntity createDataKey="customerGroup"/> </createData> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> @@ -35,21 +37,21 @@ <argument name="customerGroupName" value="$$customerGroup.code$$"/> </actionGroup> <actionGroup ref="AssertCustomerGroupNotInGridActionGroup" stepKey="assertCustomerGroupNotInGrid"> - <argument name="customerGroup" value="$$customerGroup$$" /> + <argument name="customerGroup" value="$$customerGroup$$"/> </actionGroup> <actionGroup ref="AdminOpenCustomerEditPageActionGroup" stepKey="openCustomerEditPage"> - <argument name="customerId" value="$$customer.id$$" /> + <argument name="customerId" value="$$customer.id$$"/> </actionGroup> <actionGroup ref="AssertCustomerGroupOnCustomerFormActionGroup" stepKey="assertCustomerGroupOnCustomerForm"> - <argument name="customerGroup" value="GeneralCustomerGroup" /> + <argument name="customerGroup" value="GeneralCustomerGroup"/> </actionGroup> - - <actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewProductForm" /> - + + <actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewProductForm"/> + <actionGroup ref="AssertCustomerGroupNotOnProductFormActionGroup" stepKey="assertCustomerGroupNotOnProductForm"> - <argument name="customerGroup" value="$$customerGroup$$" /> + <argument name="customerGroup" value="$$customerGroup$$"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 901018c2fd074..2b24233e8b072 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -17,6 +17,9 @@ <description value="New user signup and browses catalog, searches for product, adds product to cart, adds product to wishlist, compares products, uses coupon code and checks out."/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-87653"/> + <skip> + <issueId value="MC-17140"/> + </skip> </annotations> <before> <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml new file mode 100644 index 0000000000000..e16ec92e507e6 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/SearchByEmailInCustomerGridTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SearchByEmailInCustomerGridTest"> + <annotations> + <features value="Customer"/> + <stories value="Customer grid search"/> + <title value="Admin customer grid email searching"/> + <description value="Admin customer grid searching by email in keyword"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17985"/> + <useCaseId value="MC-17947"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createFirstCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createSecondCustomer"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <deleteData createDataKey="createFirstCustomer" stepKey="deleteFirstCustomer"/> + <deleteData createDataKey="createSecondCustomer" stepKey="deleteSecondCustomer"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <actionGroup ref="AdminResetFilterInCustomerAddressGrid" stepKey="clearCustomerGridFilter"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Step 1: Go to Customers > All Customers--> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="openCustomersGridPage"/> + <!--Step 2: On Customers grid page search customer by keyword--> + <actionGroup ref="searchAdminDataGridByKeyword" stepKey="searchCustomer"> + <argument name="keyword" value="$$createSecondCustomer.email$$"/> + </actionGroup> + <!--Step 3: Check if customer is placed in a first row and clear grid filter--> + <actionGroup ref="AdminAssertCustomerInCustomersGrid" stepKey="checkCustomerInGrid"> + <argument name="text" value="$$createSecondCustomer.email$$"/> + <argument name="row" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml index ab805193854b0..0cba9159dd5ac 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MAGETWO-95028"/> <group value="customer"/> + <skip> + <issueId value="MC-16684"/> + </skip> </annotations> <before> <!--Log In--> @@ -83,6 +86,7 @@ <!--Add a product to the cart--> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForAddProductToCart"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!--Proceed to checkout--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> <!-- Click next button to open payment section --> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml new file mode 100644 index 0000000000000..374a4be581bcf --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontClearAllCompareProductsTest"> + <annotations> + <stories value="Compare Products"/> + <title value="Clear all products from the 'Compare Products' list"/> + <description value="You should be able to remove all Products in the 'Compare Products' list."/> + <testCaseId value="MC-14208"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com"/> + <!-- Create Simple Customer --> + <createData entity="Simple_US_Customer_CA" stepKey="createSimpleCustomer1"/> + + <!-- Create Simple Category --> + <createData entity="SimpleSubCategory" stepKey="createSimpleCategory1"/> + + <!-- Create Simple Products --> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + + <!-- Create Configurable Product --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct1"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct1"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild"> + <requiredEntity createDataKey="createConfigProduct1"/> + <requiredEntity createDataKey="createConfigChildProduct"/> + </createData> + + <!-- Create Virtual Product --> + <createData entity="VirtualProduct" stepKey="createVirtualProduct1"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + + <!-- Create Bundled Product --> + <createData entity="ApiBundleProduct" stepKey="createBundleProduct1"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1"> + <requiredEntity createDataKey="createBundleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="createBundleProduct1"/> + <requiredEntity createDataKey="createBundleOption1"/> + <requiredEntity createDataKey="createSimpleProduct1"/> + <field key="qty">10</field> + </createData> + + <!-- Create Grouped Product --> + <createData entity="ApiGroupedProduct2" stepKey="createGroupedProduct1"> + <requiredEntity createDataKey="createSimpleCategory1"/> + </createData> + <createData entity="OneSimpleProductLink" stepKey="addFirstProduct1"> + <requiredEntity createDataKey="createGroupedProduct1"/> + <requiredEntity createDataKey="createSimpleProduct1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addFirstProduct1" stepKey="addSecondProduct1"> + <requiredEntity createDataKey="createGroupedProduct1"/> + <requiredEntity createDataKey="createSimpleProduct2"/> + </updateData> + + <!-- Create Downloadable Product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct1"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct1"/> + </createData> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Login --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + </before> + <after> + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com"/> + <!-- Logout --> + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> + + <!-- Delete Created Entities --> + <deleteData createDataKey="createSimpleCustomer1" stepKey="deleteSimpleCustomer1"/> + <deleteData createDataKey="createSimpleCategory1" stepKey="deleteSimpleCategory1"/> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createConfigProduct1" stepKey="deleteConfigProduct1"/> + <deleteData createDataKey="createVirtualProduct1" stepKey="deleteVirtualProduct1"/> + <deleteData createDataKey="createBundleProduct1" stepKey="deleteBundleProduct1"/> + <deleteData createDataKey="createGroupedProduct1" stepKey="deleteGroupedProduct1"/> + <deleteData createDataKey="createDownloadableProduct1" stepKey="deleteDownloadableProduct1"/> + </after> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer1"> + <argument name="Customer" value="$$createSimpleCustomer1$$" /> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage1"> + <argument name="productUrl" value="$$createSimpleProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton1"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare1"> + <argument name="productVar" value="$$createSimpleProduct1$$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage2"> + <argument name="productUrl" value="$$createConfigProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton2"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare2"> + <argument name="productVar" value="$$createConfigProduct1$$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage3"> + <argument name="productUrl" value="$$createVirtualProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton3"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare3"> + <argument name="productVar" value="$$createVirtualProduct1$$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage4"> + <argument name="productUrl" value="$$createBundleProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton4"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare4"> + <argument name="productVar" value="$$createBundleProduct1$$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage5"> + <argument name="productUrl" value="$$createGroupedProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton5"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare5"> + <argument name="productVar" value="$$createGroupedProduct1$$"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage6"> + <argument name="productUrl" value="$$createDownloadableProduct1.custom_attributes[url_key]$$"/> + </actionGroup> + <scrollTo selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="scrollToCompareProductButton6"/> + <actionGroup ref="StorefrontAddProductToCompareActionGroup" stepKey="addProductToCompare6"> + <argument name="productVar" value="$$createDownloadableProduct1$$"/> + </actionGroup> + + <amOnPage url="{{StorefrontCustomerDashboardPage.url}}" stepKey="amOnMyAccountDashboard1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + + <actionGroup ref="StorefrontClearCompareActionGroup" stepKey="clearComparedProducts1"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml new file mode 100644 index 0000000000000..952ac235d92a4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCreateExistingCustomerTest"> + <annotations> + <features value="Customer"/> + <stories value="Customer Registration"/> + <title value="Attempt to register customer on storefront with existing email"/> + <description value="Attempt to register customer on storefront with existing email"/> + <testCaseId value="MC-10907"/> + <severity value="MAJOR"/> + <group value="customers"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeErrorMessage"> + <argument name="messageType" value="error"/> + <argument name="message" value="There is already an account with this email address."/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLockCustomerOnLoginPageTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLockCustomerOnLoginPageTest.xml new file mode 100644 index 0000000000000..c69c4dd071e38 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLockCustomerOnLoginPageTest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontLockCustomerOnLoginPageTest"> + <annotations> + <features value="Customer"/> + <stories value="Lock Customer entering incorrect login credentials"/> + <title value="Lock customer on Storefront with after many attempts to log in with incorrect credentials"/> + <description value="Lock customer on Storefront with after many attempts to log in with incorrect credentials"/> + <testCaseId value="MC-14388"/> + <severity value="CRITICAL"/> + <group value="customer"/> + <group value="security"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerLockoutFailures5ConfigData.path}} {{StorefrontCustomerLockoutFailures5ConfigData.value}}" stepKey="setInvalidAttemptsCountConfigTo5"/> + <createData stepKey="customer" entity="Simple_US_Customer"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerLockoutFailuresDefaultConfigData.path}} {{StorefrontCustomerLockoutFailuresDefaultConfigData.value}}" stepKey="revertInvalidAttemptsCountConfig"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + </after> + + <actionGroup ref="StorefrontOpenCustomerLoginPageActionGroup" stepKey="goToSignInPage"/> + + <!-- Perform 5 attempts to log in with invalid credentials --> + <actionGroup ref="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" stepKey="fillLoginFormFirstAttempt"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFirstAttempt"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFirstAttempt"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later"/> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" stepKey="fillLoginFormSecondAttempt"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonSecondAttempt"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterSecondAttempt"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later"/> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" stepKey="fillLoginFormThirdAttempt"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonThirdAttempt"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterThirdAttempt"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later"/> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" stepKey="fillLoginFormFourthAttempt"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFourthAttempt"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFourthAttempt"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later"/> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithWrongPasswordActionGroup" stepKey="fillLoginFormFifthAttempt"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFifthAttempt"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFifthAttempt"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later"/> + </actionGroup> + + <!-- Make sure that the customer is locked --> + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormWithCorrectCredentials"> + <argument name="customer" value="$$customer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonWithCorrectCredentials"/> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeLockoutErrorMessage"> + <argument name="messageType" value="error"/> + <argument name="message" value="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later."/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml index 3121bd0da9d2d..5d0eec935e192 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontResetCustomerPasswordFailedTest"> <annotations> <features value="Customer"/> + <stories value="Reset password"/> <title value="Customer tries to reset password several times"/> <description value="Customer tries to reset password several times"/> <severity value="CRITICAL" /> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml index 648c30b1ca0bb..0f98184aafb4f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml @@ -26,7 +26,7 @@ <!-- 2. Navigate to Customers > Customer Groups --> <amOnPage url="{{AdminCustomerGroupPage.url}}" stepKey="amOnCustomerGroupPage" /> - <waitForPageLoad stepKey="waitForCustomerGroupsPageLoad" /> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFiltersIfTheySet"/> <!-- 3. Select system Customer Group specified in data set from grid --> <click selector="{{AdminCustomerGroupMainSection.editButtonByCustomerGroupCode(NotLoggedInCustomerGroup.code)}}" stepKey="clickOnEditCustomerGroup" /> diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php index 762c76b695dee..13cf195ab5f69 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php @@ -93,13 +93,14 @@ protected function setUp() $this->session = $this->getMockBuilder(\Magento\Customer\Model\Session::class) ->disableOriginalConstructor() - ->setMethods([ - 'isLoggedIn', - 'setCustomerDataAsLoggedIn', - 'regenerateId', - 'setUsername', - ]) - ->getMock(); + ->setMethods( + [ + 'isLoggedIn', + 'setCustomerDataAsLoggedIn', + 'regenerateId', + 'setUsername', + ] + )->getMock(); $this->accountManagement = $this->getMockBuilder(\Magento\Customer\Api\AccountManagementInterface::class) ->getMockForAbstractClass(); @@ -253,10 +254,12 @@ public function testExecuteSuccessCustomRedirect() $this->request->expects($this->once()) ->method('getPost') ->with('login') - ->willReturn([ - 'username' => $username, - 'password' => $password, - ]); + ->willReturn( + [ + 'username' => $username, + 'password' => $password, + ] + ); $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) ->getMockForAbstractClass(); @@ -292,9 +295,8 @@ public function testExecuteSuccessCustomRedirect() ->method('setCustomerDataAsLoggedIn') ->with($customerMock) ->willReturnSelf(); - $this->session->expects($this->once()) - ->method('regenerateId') - ->willReturnSelf(); + $this->session->expects($this->never()) + ->method('regenerateId'); $this->accountRedirect->expects($this->never()) ->method('getRedirect') @@ -335,10 +337,12 @@ public function testExecuteSuccess() $this->request->expects($this->once()) ->method('getPost') ->with('login') - ->willReturn([ - 'username' => $username, - 'password' => $password, - ]); + ->willReturn( + [ + 'username' => $username, + 'password' => $password, + ] + ); $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) ->getMockForAbstractClass(); @@ -357,9 +361,8 @@ public function testExecuteSuccess() ->method('setCustomerDataAsLoggedIn') ->with($customerMock) ->willReturnSelf(); - $this->session->expects($this->once()) - ->method('regenerateId') - ->willReturnSelf(); + $this->session->expects($this->never()) + ->method('regenerateId'); $this->accountRedirect->expects($this->once()) ->method('getRedirect') @@ -426,10 +429,12 @@ public function testExecuteWithException( $this->request->expects($this->once()) ->method('getPost') ->with('login') - ->willReturn([ - 'username' => $username, - 'password' => $password, - ]); + ->willReturn( + [ + 'username' => $username, + 'password' => $password, + ] + ); $exception = new $exceptionData['exception'](__($exceptionData['message'])); @@ -488,11 +493,12 @@ protected function prepareContext() $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) ->disableOriginalConstructor() - ->setMethods([ - 'isPost', - 'getPost', - ]) - ->getMock(); + ->setMethods( + [ + 'isPost', + 'getPost', + ] + )->getMock(); $this->resultRedirect = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php index 45e64f6557d51..8267624f7b006 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\EmailNotificationInterface; use Magento\Framework\DataObject; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Escaper; /** * Unit tests for Inline customer edit @@ -78,6 +79,9 @@ class InlineEditTest extends \PHPUnit\Framework\TestCase /** @var array */ private $items; + /** @var \Magento\Framework\Escaper */ + private $escaper; + /** * Sets up mocks * @@ -86,7 +90,7 @@ class InlineEditTest extends \PHPUnit\Framework\TestCase protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - + $this->escaper = new Escaper(); $this->request = $this->getMockForAbstractClass( \Magento\Framework\App\RequestInterface::class, [], @@ -172,7 +176,8 @@ protected function setUp() 'addressDataFactory' => $this->addressDataFactory, 'addressRepository' => $this->addressRepository, 'logger' => $this->logger, - 'addressRegistry' => $this->addressRegistry + 'addressRegistry' => $this->addressRegistry, + 'escaper' => $this->escaper, ] ); $reflection = new \ReflectionClass(get_class($this->controller)); @@ -291,10 +296,14 @@ protected function prepareMocksForErrorMessagesProcessing() ->willReturn('Error text'); $this->resultJson->expects($this->once()) ->method('setData') - ->with([ - 'messages' => ['Error text'], - 'error' => true, - ]) + ->with( + [ + 'messages' => [ + 'Error text', + ], + 'error' => true, + ] + ) ->willReturnSelf(); } @@ -340,10 +349,14 @@ public function testExecuteWithoutItems() $this->resultJson ->expects($this->once()) ->method('setData') - ->with([ - 'messages' => [__('Please correct the data sent.')], - 'error' => true, - ]) + ->with( + [ + 'messages' => [ + __('Please correct the data sent.'), + ], + 'error' => true, + ] + ) ->willReturnSelf(); $this->assertSame($this->resultJson, $this->controller->execute()); } @@ -365,6 +378,7 @@ public function testExecuteLocalizedException() ->method('save') ->with($this->customerData) ->willThrowException($exception); + $this->messageManager->expects($this->once()) ->method('addError') ->with('[Customer ID: 12] Exception message'); diff --git a/app/code/Magento/Customer/Test/Unit/Helper/Session/CurrentCustomerTest.php b/app/code/Magento/Customer/Test/Unit/Helper/Session/CurrentCustomerTest.php index 364c3700cab26..15ced1ce66d06 100644 --- a/app/code/Magento/Customer/Test/Unit/Helper/Session/CurrentCustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Helper/Session/CurrentCustomerTest.php @@ -6,6 +6,9 @@ namespace Magento\Customer\Test\Unit\Helper\Session; +/** + * Current customer test. + */ class CurrentCustomerTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 6a6bad74d1b52..3c38cd0f7b4e2 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -12,6 +12,7 @@ use Magento\Customer\Model\AuthenticationInterface; use Magento\Customer\Model\Data\Customer; use Magento\Customer\Model\EmailNotificationInterface; +use Magento\Directory\Model\AllowedCountries; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Area; use Magento\Framework\Exception\NoSuchEntityException; @@ -155,6 +156,11 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase */ private $searchCriteriaBuilderMock; + /** + * @var AllowedCountries|\PHPUnit_Framework_MockObject_MockObject + */ + private $allowedCountriesReader; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -193,6 +199,7 @@ protected function setUp() $this->extensibleDataObjectConverter = $this->createMock( \Magento\Framework\Api\ExtensibleDataObjectConverter::class ); + $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -256,6 +263,7 @@ protected function setUp() 'visitorCollectionFactory' => $this->visitorCollectionFactory, 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, 'addressRegistry' => $this->addressRegistryMock, + 'allowedCountriesReader' => $this->allowedCountriesReader, ] ); $this->objectManagerHelper->setBackwardCompatibleProperty( @@ -551,7 +559,14 @@ public function testCreateAccountWithPasswordHashWithAddressException() ->expects($this->once()) ->method('delete') ->with($customer); - + $this->allowedCountriesReader + ->expects($this->atLeastOnce()) + ->method('getAllowedCountries') + ->willReturn(['US' => 'US']); + $address + ->expects($this->atLeastOnce()) + ->method('getCountryId') + ->willReturn('US'); $this->accountManagement->createAccountWithPasswordHash($customer, $hash); } @@ -725,6 +740,14 @@ public function testCreateAccountWithoutPassword() $this->emailNotificationMock->expects($this->once()) ->method('newAccount') ->willReturnSelf(); + $this->allowedCountriesReader + ->expects($this->atLeastOnce()) + ->method('getAllowedCountries') + ->willReturn(['US' => 'US']); + $address + ->expects($this->atLeastOnce()) + ->method('getCountryId') + ->willReturn('US'); $this->accountManagement->createAccount($customer); } @@ -793,14 +816,18 @@ public function testCreateAccountWithPasswordInputException( if ($testNumber == 1) { $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage('The password needs at least ' . $minPasswordLength . ' characters. ' - . 'Create a new password and try again.'); + $this->expectExceptionMessage( + 'The password needs at least ' . $minPasswordLength . ' characters. ' + . 'Create a new password and try again.' + ); } if ($testNumber == 2) { $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage('Minimum of different classes of characters in password is ' . - $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.'); + $this->expectExceptionMessage( + 'Minimum of different classes of characters in password is ' . + $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.' + ); } $customer = $this->getMockBuilder(Customer::class)->disableOriginalConstructor()->getMock(); @@ -970,6 +997,14 @@ public function testCreateAccountWithPassword() $this->emailNotificationMock->expects($this->once()) ->method('newAccount') ->willReturnSelf(); + $this->allowedCountriesReader + ->expects($this->atLeastOnce()) + ->method('getAllowedCountries') + ->willReturn(['US' => 'US']); + $address + ->expects($this->atLeastOnce()) + ->method('getCountryId') + ->willReturn('US'); $this->accountManagement->createAccount($customer, $password); } @@ -1951,6 +1986,14 @@ public function testCreateAccountWithPasswordHashWithCustomerAddresses() ->method('getWebsite') ->with($websiteId) ->willReturn($website); + $this->allowedCountriesReader + ->expects($this->atLeastOnce()) + ->method('getAllowedCountries') + ->willReturn(['US' => 'US']); + $existingAddress + ->expects($this->atLeastOnce()) + ->method('getCountryId') + ->willReturn('US'); $this->assertSame($customer, $this->accountManagement->createAccountWithPasswordHash($customer, $hash)); } @@ -2078,7 +2121,9 @@ public function testCreateAccountUnexpectedValueException(): void ->method('newAccount') ->willThrowException($exception); $this->logger->expects($this->once())->method('error')->with($exception); - + $this->allowedCountriesReader->expects($this->atLeastOnce()) + ->method('getAllowedCountries')->willReturn(['US' => 'US']); + $address->expects($this->atLeastOnce())->method('getCountryId')->willReturn('US'); $this->accountManagement->createAccount($customer); } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php index f26a5ba2dbb76..1e5d44e22adcb 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php @@ -35,11 +35,17 @@ protected function setUp() \Magento\Directory\Model\AllowedCountries::class, ['getAllowedCountries'] ); + + $escaper = $this->objectManager->getObject( + \Magento\Framework\Escaper::class + ); + $this->model = $this->objectManager->getObject( \Magento\Customer\Model\Address\Validator\Country::class, [ 'directoryData' => $this->directoryDataMock, 'allowedCountriesReader' => $this->allowedCountriesReaderMock, + 'escaper' => $escaper ] ); } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php index ac87e2e336e3d..8ad7363a1c310 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php @@ -3,23 +3,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Model\Customer; use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Model\Address; use Magento\Customer\Model\Config\Share; +use Magento\Customer\Model\Customer; +use Magento\Customer\Model\Customer\DataProvider as CustomerDataProvider; +use Magento\Customer\Model\FileProcessor; +use Magento\Customer\Model\FileUploaderDataResolver; use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Customer\Model\ResourceModel\Customer\Collection; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; use Magento\Eav\Model\Entity\Type; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Session\SessionManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Component\Form\Field; use Magento\Ui\DataProvider\EavValidationRules; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class DataProviderTest - * - * Test for class \Magento\Customer\Model\Customer\DataProvider + * Unit tests for \Magento\Customer\Model\Customer\DataProvider class. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -29,69 +39,64 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase const OPTIONS_RESULT = 'test-options'; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ protected $eavConfigMock; /** - * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|MockObject */ protected $customerCollectionFactoryMock; /** - * @var EavValidationRules|\PHPUnit_Framework_MockObject_MockObject + * @var EavValidationRules|MockObject */ protected $eavValidationRulesMock; /** - * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var SessionManagerInterface|MockObject */ protected $sessionMock; /** - * @var \Magento\Customer\Model\FileProcessor|\PHPUnit_Framework_MockObject_MockObject + * @var FileProcessor|MockObject */ protected $fileProcessor; /** - * @var \Magento\Customer\Model\FileUploaderDataResolver|\PHPUnit_Framework_MockObject_MockObject + * @var FileUploaderDataResolver|MockObject */ private $fileUploaderDataResolver; /** - * Set up - * - * @return void + * @inheritdoc */ protected function setUp() { - $this->eavConfigMock = $this->getMockBuilder(\Magento\Eav\Model\Config::class) + $this->eavConfigMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->customerCollectionFactoryMock = $this->createPartialMock( - \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class, - ['create'] - ); + $this->customerCollectionFactoryMock = $this->createPartialMock(CollectionFactory::class, ['create']); $this->eavValidationRulesMock = $this - ->getMockBuilder(\Magento\Ui\DataProvider\EavValidationRules::class) + ->getMockBuilder(EavValidationRules::class) ->disableOriginalConstructor() ->getMock(); $this->sessionMock = $this - ->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) + ->getMockBuilder(SessionManagerInterface::class) ->setMethods(['getCustomerFormData', 'unsCustomerFormData']) ->getMockForAbstractClass(); - $this->fileProcessor = $this->getMockBuilder(\Magento\Customer\Model\FileProcessor::class) + $this->fileProcessor = $this->getMockBuilder(FileProcessor::class) ->disableOriginalConstructor() ->getMock(); - $this->fileUploaderDataResolver = $this->getMockBuilder(\Magento\Customer\Model\FileUploaderDataResolver::class) + $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class) ->disableOriginalConstructor() ->setMethods(['overrideFileUploaderMetadata', 'overrideFileUploaderData']) ->getMock(); } /** - * Run test getAttributesMeta method + * Run test getAttributesMeta method. * * @param array $expected * @return void @@ -101,9 +106,9 @@ protected function setUp() public function testGetAttributesMetaWithOptions(array $expected) { $helper = new ObjectManager($this); - /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */ + /** @var CustomerDataProvider $dataProvider */ $dataProvider = $helper->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -111,7 +116,7 @@ public function testGetAttributesMetaWithOptions(array $expected) 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), 'eavConfig' => $this->getEavConfigMock(), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); @@ -121,7 +126,7 @@ public function testGetAttributesMetaWithOptions(array $expected) } /** - * Data provider for testGetAttributesMeta + * Data provider for testGetAttributesMeta. * * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -238,8 +243,8 @@ public function getAttributesMetaDataProvider() 'componentType' => Field::NAME, 'filterBy' => [ 'target' => '${ $.provider }:data.customer.website_id', - 'field' => 'website_ids' - ] + 'field' => 'website_ids', + ], ], ], ], @@ -252,11 +257,11 @@ public function getAttributesMetaDataProvider() } /** - * @return CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @return CollectionFactory|MockObject */ protected function getCustomerCollectionFactoryMock() { - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) + $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); @@ -272,7 +277,7 @@ protected function getCustomerCollectionFactoryMock() } /** - * @return Config|\PHPUnit_Framework_MockObject_MockObject + * @return Config|MockObject */ protected function getEavConfigMock($customerAttributes = []) { @@ -289,11 +294,11 @@ protected function getEavConfigMock($customerAttributes = []) } /** - * @return Type|\PHPUnit_Framework_MockObject_MockObject + * @return Type|MockObject */ protected function getTypeCustomerMock($customerAttributes = []) { - $typeCustomerMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + $typeCustomerMock = $this->getMockBuilder(Type::class) ->disableOriginalConstructor() ->getMock(); $attributesCollection = !empty($customerAttributes) ? $customerAttributes : $this->getAttributeMock(); @@ -314,11 +319,11 @@ protected function getTypeCustomerMock($customerAttributes = []) } /** - * @return Type|\PHPUnit_Framework_MockObject_MockObject + * @return Type|MockObject */ protected function getTypeAddressMock() { - $typeAddressMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + $typeAddressMock = $this->getMockBuilder(Type::class) ->disableOriginalConstructor() ->getMock(); @@ -330,13 +335,13 @@ protected function getTypeAddressMock() } /** - * @param \PHPUnit_Framework_MockObject_MockObject $attributeMock - * @param \PHPUnit_Framework_MockObject_MockObject $attributeBooleanMock + * @param MockObject $attributeMock + * @param MockObject $attributeBooleanMock * @param array $options */ private function injectVisibilityProps( - \PHPUnit_Framework_MockObject_MockObject $attributeMock, - \PHPUnit_Framework_MockObject_MockObject $attributeBooleanMock, + MockObject $attributeMock, + MockObject $attributeBooleanMock, array $options = [] ) { if (isset($options[self::ATTRIBUTE_CODE]['visible'])) { @@ -377,11 +382,11 @@ private function injectVisibilityProps( } /** - * @return AbstractAttribute[]|\PHPUnit_Framework_MockObject_MockObject[] + * @return AbstractAttribute[]|MockObject[] */ protected function getAttributeMock($type = 'customer', $options = []) { - $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + $attributeMock = $this->getMockBuilder(AbstractAttribute::class) ->setMethods( [ 'getAttributeCode', @@ -397,7 +402,7 @@ protected function getAttributeMock($type = 'customer', $options = []) ) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $sourceMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource::class) + $sourceMock = $this->getMockBuilder(AbstractSource::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -425,7 +430,7 @@ protected function getAttributeMock($type = 'customer', $options = []) ->method('getSource') ->willReturn($sourceMock); - $attributeBooleanMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + $attributeBooleanMock = $this->getMockBuilder(AbstractAttribute::class) ->setMethods( [ 'getAttributeCode', @@ -463,10 +468,12 @@ protected function getAttributeMock($type = 'customer', $options = []) $this->eavValidationRulesMock->expects($this->any()) ->method('build') - ->willReturnMap([ - [$attributeMock, $this->logicalNot($this->isEmpty()), []], - [$attributeBooleanMock, $this->logicalNot($this->isEmpty()), []], - ]); + ->willReturnMap( + [ + [$attributeMock, $this->logicalNot($this->isEmpty()), []], + [$attributeBooleanMock, $this->logicalNot($this->isEmpty()), []], + ] + ); $mocks = [$attributeMock, $attributeBooleanMock]; $this->injectVisibilityProps($attributeMock, $attributeBooleanMock, $options); if ($type == "address") { @@ -476,7 +483,7 @@ protected function getAttributeMock($type = 'customer', $options = []) } /** - * Callback for ::getDataUsingMethod + * Callback for ::getDataUsingMethod. * * @return \Closure */ @@ -488,7 +495,7 @@ private function attributeGetUsingMethodCallback() } /** - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ private function getCountryAttrMock() { @@ -501,15 +508,17 @@ private function getCountryAttrMock() $shareMock = $this->getMockBuilder(Share::class) ->disableOriginalConstructor() ->getMock(); - $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); + $objectManagerMock = $this->createMock(ObjectManagerInterface::class); $objectManagerMock->expects($this->any()) ->method('get') - ->willReturnMap([ - [CountryWithWebsites::class, $countryByWebsiteMock], - [Share::class, $shareMock], - ]); + ->willReturnMap( + [ + [CountryWithWebsites::class, $countryByWebsiteMock], + [Share::class, $shareMock], + ] + ); \Magento\Framework\App\ObjectManager::setInstance($objectManagerMock); - $countryAttrMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + $countryAttrMock = $this->getMockBuilder(AbstractAttribute::class) ->setMethods(['getAttributeCode', 'getDataUsingMethod', 'usesSource', 'getSource', 'getLabel']) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -558,13 +567,13 @@ public function testGetData() 'street' => "street\nstreet", ]; - $customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + $customer = $this->getMockBuilder(Customer::class) ->disableOriginalConstructor() ->getMock(); - $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + $address = $this->getMockBuilder(Address::class) ->disableOriginalConstructor() ->getMock(); - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) + $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); @@ -598,7 +607,7 @@ public function testGetData() $helper = new ObjectManager($this); $dataProvider = $helper->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -606,7 +615,7 @@ public function testGetData() 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, 'eavConfig' => $this->getEavConfigMock(), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); @@ -635,9 +644,9 @@ public function testGetData() 'street' => "street\nstreet", 'default_billing' => 2, 'default_shipping' => 2, - ] - ] - ] + ], + ], + ], ], $dataProvider->getData() ); @@ -671,13 +680,13 @@ public function testGetDataWithCustomerFormData() ], ]; - $customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + $customer = $this->getMockBuilder(Customer::class) ->disableOriginalConstructor() ->getMock(); - $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + $address = $this->getMockBuilder(Address::class) ->disableOriginalConstructor() ->getMock(); - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) + $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); @@ -694,11 +703,13 @@ public function testGetDataWithCustomerFormData() ->willReturn([$customer]); $customer->expects($this->once()) ->method('getData') - ->willReturn([ - 'email' => 'test@test.ua', - 'default_billing' => 2, - 'default_shipping' => 2, - ]); + ->willReturn( + [ + 'email' => 'test@test.ua', + 'default_billing' => 2, + 'default_shipping' => 2, + ] + ); $customer->expects($this->once()) ->method('getId') ->willReturn($customerId); @@ -714,14 +725,16 @@ public function testGetDataWithCustomerFormData() ->willReturnSelf(); $address->expects($this->once()) ->method('getData') - ->willReturn([ - 'firstname' => 'firstname', - 'lastname' => 'lastname', - 'street' => "street\nstreet", - ]); + ->willReturn( + [ + 'firstname' => 'firstname', + 'lastname' => 'lastname', + 'street' => "street\nstreet", + ] + ); $helper = new ObjectManager($this); $dataProvider = $helper->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -729,7 +742,7 @@ public function testGetDataWithCustomerFormData() 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, 'eavConfig' => $this->getEavConfigMock(), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); @@ -758,22 +771,24 @@ public function testGetDataWithCustomAttributeImage() $filename = '/filename.ext1'; - $customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) + $customerMock = $this->getMockBuilder(Customer::class) ->disableOriginalConstructor() ->getMock(); $customerMock->expects($this->once()) ->method('getData') - ->willReturn([ - 'email' => $customerEmail, - 'img1' => $filename, - ]); + ->willReturn( + [ + 'email' => $customerEmail, + 'img1' => $filename, + ] + ); $customerMock->expects($this->once()) ->method('getAddresses') ->willReturn([]); $customerMock->expects($this->once()) ->method('getId') ->willReturn($customerId); - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) + $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $collectionMock->expects($this->once()) @@ -790,7 +805,7 @@ public function testGetDataWithCustomAttributeImage() $objectManager = new ObjectManager($this); $dataProvider = $objectManager->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -798,7 +813,7 @@ public function testGetDataWithCustomAttributeImage() 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, 'eavConfig' => $this->getEavConfigMock(), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); @@ -830,7 +845,7 @@ public function testGetAttributesMetaWithCustomAttributeImage() $attributeCode = 'img1'; - $collectionMock = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) + $collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $collectionMock->expects($this->once()) @@ -841,12 +856,14 @@ public function testGetAttributesMetaWithCustomAttributeImage() ->method('create') ->willReturn($collectionMock); - $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) - ->setMethods([ - 'getAttributeCode', - 'getFrontendInput', - 'getDataUsingMethod', - ]) + $attributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getAttributeCode', + 'getFrontendInput', + 'getDataUsingMethod', + ] + ) ->disableOriginalConstructor() ->getMockForAbstractClass(); $attributeMock->expects($this->any()) @@ -863,7 +880,7 @@ function ($origName) { } ); - $typeCustomerMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + $typeCustomerMock = $this->getMockBuilder(Type::class) ->disableOriginalConstructor() ->getMock(); $typeCustomerMock->expects($this->once()) @@ -873,7 +890,7 @@ function ($origName) { ->method('getEntityTypeCode') ->willReturn(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER); - $typeAddressMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + $typeAddressMock = $this->getMockBuilder(Type::class) ->disableOriginalConstructor() ->getMock(); $typeAddressMock->expects($this->once()) @@ -891,32 +908,37 @@ function ($origName) { $this->eavValidationRulesMock->expects($this->once()) ->method('build') - ->with($attributeMock, [ - 'dataType' => 'frontend_input', - 'formElement' => 'frontend_input', - 'visible' => 'is_visible', - 'required' => 'is_required', - 'sortOrder' => 'sort_order', - 'notice' => 'note', - 'default' => 'default_value', - 'size' => 'multiline_count', - 'label' => __('frontend_label'), - ]) - ->willReturn([ - 'max_file_size' => $maxFileSize, - 'file_extensions' => 'ext1, eXt2 ', // Added spaces and upper-cases - ]); + ->with( + $attributeMock, + [ + 'dataType' => 'frontend_input', + 'formElement' => 'frontend_input', + 'visible' => 'is_visible', + 'required' => 'is_required', + 'sortOrder' => 'sort_order', + 'notice' => 'note', + 'default' => 'default_value', + 'size' => 'multiline_count', + 'label' => __('frontend_label'), + ] + ) + ->willReturn( + [ + 'max_file_size' => $maxFileSize, + 'file_extensions' => 'ext1, eXt2 ', // Added spaces and upper-cases + ] + ); $objectManager = new ObjectManager($this); $dataProvider = $objectManager->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', 'requestFieldName' => 'request-field-name', 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->customerCollectionFactoryMock, - 'eavConfig' => $this->eavConfigMock + 'eavConfig' => $this->eavConfigMock, ] ); @@ -931,6 +953,7 @@ function ($origName) { 'arguments' => [ 'data' => [ 'config' => [ + 'dataType' => 'frontend_input', 'formElement' => 'fileUploader', 'componentType' => 'fileUploader', 'maxFileSize' => $maxFileSize, @@ -973,13 +996,13 @@ public function testGetDataWithVisibleAttributes() 'visible' => true, 'is_used_in_forms' => ['customer_account_edit'], 'user_defined' => true, - 'specific_code_prefix' => "_1" + 'specific_code_prefix' => "_1", ], 'test-code-boolean' => [ 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => true, - 'specific_code_prefix' => "_1" + 'specific_code_prefix' => "_1", ] ] ); @@ -990,21 +1013,21 @@ public function testGetDataWithVisibleAttributes() 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => false, - 'specific_code_prefix' => "_2" + 'specific_code_prefix' => "_2", ], 'test-code-boolean' => [ 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => true, - 'specific_code_prefix' => "_2" + 'specific_code_prefix' => "_2", ] ] ); $helper = new ObjectManager($this); - /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */ + /** @var DataProvider $dataProvider */ $dataProvider = $helper->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -1012,7 +1035,7 @@ public function testGetDataWithVisibleAttributes() 'eavValidationRules' => $this->eavValidationRulesMock, 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); @@ -1033,13 +1056,13 @@ public function testGetDataWithVisibleAttributesWithAccountEdit() 'visible' => true, 'is_used_in_forms' => ['customer_account_edit'], 'user_defined' => true, - 'specific_code_prefix' => "_1" + 'specific_code_prefix' => "_1", ], 'test-code-boolean' => [ 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => true, - 'specific_code_prefix' => "_1" + 'specific_code_prefix' => "_1", ] ] ); @@ -1050,28 +1073,28 @@ public function testGetDataWithVisibleAttributesWithAccountEdit() 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => false, - 'specific_code_prefix' => "_2" + 'specific_code_prefix' => "_2", ], 'test-code-boolean' => [ 'visible' => true, 'is_used_in_forms' => ['customer_account_create'], 'user_defined' => true, - 'specific_code_prefix' => "_2" + 'specific_code_prefix' => "_2", ] ] ); $helper = new ObjectManager($this); - $context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) + $context = $this->getMockBuilder(ContextInterface::class) ->setMethods(['getRequestParam']) ->getMockForAbstractClass(); $context->expects($this->any()) ->method('getRequestParam') ->with('request-field-name') ->willReturn(1); - /** @var \Magento\Customer\Model\Customer\DataProvider $dataProvider */ + /** @var DataProvider $dataProvider */ $dataProvider = $helper->getObject( - \Magento\Customer\Model\Customer\DataProvider::class, + CustomerDataProvider::class, [ 'name' => 'test-name', 'primaryFieldName' => 'primary-field-name', @@ -1080,23 +1103,22 @@ public function testGetDataWithVisibleAttributesWithAccountEdit() 'customerCollectionFactory' => $this->getCustomerCollectionFactoryMock(), 'context' => $context, 'eavConfig' => $this->getEavConfigMock(array_merge($firstAttributesBundle, $secondAttributesBundle)), - 'fileUploaderDataResolver' => $this->fileUploaderDataResolver + 'fileUploaderDataResolver' => $this->fileUploaderDataResolver, ] ); $meta = $dataProvider->getMeta(); $this->assertNotEmpty($meta); - $this->assertEquals($this->getExpectationForVisibleAttributes(false), $meta); + $this->assertEquals($this->getExpectationForVisibleAttributes(), $meta); } /** - * Retrieve all customer variations of attributes with all variations of visibility + * Retrieve all customer variations of attributes with all variations of visibility. * - * @param bool $isRegistration * @return array */ - private function getCustomerAttributeExpectations($isRegistration) + private function getCustomerAttributeExpectations() { return [ self::ATTRIBUTE_CODE . "_1" => [ @@ -1106,7 +1128,7 @@ private function getCustomerAttributeExpectations($isRegistration) 'dataType' => 'frontend_input', 'formElement' => 'frontend_input', 'options' => 'test-options', - 'visible' => !$isRegistration, + 'visible' => true, 'required' => 'is_required', 'label' => __('frontend_label'), 'sortOrder' => 'sort_order', @@ -1143,7 +1165,7 @@ private function getCustomerAttributeExpectations($isRegistration) 'config' => [ 'dataType' => 'frontend_input', 'formElement' => 'frontend_input', - 'visible' => $isRegistration, + 'visible' => true, 'required' => 'is_required', 'label' => __('frontend_label'), 'sortOrder' => 'sort_order', @@ -1166,7 +1188,7 @@ private function getCustomerAttributeExpectations($isRegistration) 'config' => [ 'dataType' => 'frontend_input', 'formElement' => 'frontend_input', - 'visible' => $isRegistration, + 'visible' => true, 'required' => 'is_required', 'label' => __('frontend_label'), 'sortOrder' => 'sort_order', @@ -1187,16 +1209,15 @@ private function getCustomerAttributeExpectations($isRegistration) } /** - * Retrieve all variations of attributes with all variations of visibility + * Retrieve all variations of attributes with all variations of visibility. * - * @param bool $isRegistration * @return array */ - private function getExpectationForVisibleAttributes($isRegistration = true) + private function getExpectationForVisibleAttributes() { return [ 'customer' => [ - 'children' => $this->getCustomerAttributeExpectations($isRegistration), + 'children' => $this->getCustomerAttributeExpectations(), ], 'address' => [ 'children' => [ @@ -1259,12 +1280,12 @@ private function getExpectationForVisibleAttributes($isRegistration = true) 'componentType' => Field::NAME, 'filterBy' => [ 'target' => '${ $.provider }:data.customer.website_id', - 'field' => 'website_ids' - ] + 'field' => 'website_ids', + ], ], ], ], - ] + ], ], ], ]; diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/Source/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/Source/GroupTest.php index e07f2f0add972..bc4c19bc23ac1 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/Source/GroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/Source/GroupTest.php @@ -12,6 +12,9 @@ use Magento\Framework\Api\SearchCriteria; use Magento\Customer\Api\Data\GroupSearchResultsInterface; +/** + * Group test. + */ class GroupTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerAuthUpdateTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerAuthUpdateTest.php index a1a243066bb7d..81a612c519f52 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerAuthUpdateTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerAuthUpdateTest.php @@ -5,7 +5,14 @@ */ namespace Magento\Customer\Test\Unit\Model; +use Magento\Customer\Model\Customer as CustomerModel; use Magento\Customer\Model\CustomerAuthUpdate; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\Data\CustomerSecure; +use Magento\Customer\Model\ResourceModel\Customer as CustomerResourceModel; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class CustomerAuthUpdateTest @@ -18,17 +25,22 @@ class CustomerAuthUpdateTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \Magento\Customer\Model\CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject + * @var CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject */ protected $customerRegistry; /** - * @var \Magento\Customer\Model\ResourceModel\Customer|\PHPUnit_Framework_MockObject_MockObject + * @var CustomerResourceModel|\PHPUnit_Framework_MockObject_MockObject */ protected $customerResourceModel; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var CustomerModel|\PHPUnit_Framework_MockObject_MockObject + */ + protected $customerModel; + + /** + * @var ObjectManager */ protected $objectManager; @@ -37,32 +49,36 @@ class CustomerAuthUpdateTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->objectManager = new ObjectManager($this); $this->customerRegistry = - $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); + $this->createMock(CustomerRegistry::class); $this->customerResourceModel = - $this->createMock(\Magento\Customer\Model\ResourceModel\Customer::class); + $this->createMock(CustomerResourceModel::class); + $this->customerModel = + $this->createMock(CustomerModel::class); $this->model = $this->objectManager->getObject( - \Magento\Customer\Model\CustomerAuthUpdate::class, + CustomerAuthUpdate::class, [ 'customerRegistry' => $this->customerRegistry, 'customerResourceModel' => $this->customerResourceModel, + 'customerModel' => $this->customerModel ] ); } /** * test SaveAuth + * @throws NoSuchEntityException */ public function testSaveAuth() { $customerId = 1; - $customerSecureMock = $this->createMock(\Magento\Customer\Model\Data\CustomerSecure::class); + $customerSecureMock = $this->createMock(CustomerSecure::class); - $dbAdapter = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $dbAdapter = $this->createMock(AdapterInterface::class); $this->customerRegistry->expects($this->once()) ->method('retrieveSecureData') @@ -98,6 +114,9 @@ public function testSaveAuth() $customerId ); + $this->customerModel->expects($this->once()) + ->method('reindex'); + $this->model->saveAuth($customerId); } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index 65831069aa1fb..170cd001e5b9e 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -15,6 +15,7 @@ use Magento\Customer\Model\AccountConfirmation; use Magento\Customer\Model\ResourceModel\Address\CollectionFactory as AddressCollectionFactory; use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Framework\Math\Random; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -86,6 +87,14 @@ class CustomerTest extends \PHPUnit\Framework\TestCase */ private $dataObjectHelper; + /** + * @var Random|\PHPUnit_Framework_MockObject_MockObject + */ + private $mathRandom; + + /** + * @inheritdoc + */ protected function setUp() { $this->_website = $this->createMock(\Magento\Store\Model\Website::class); @@ -130,6 +139,7 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['populateWithArray']) ->getMock(); + $this->mathRandom = $this->createMock(Random::class); $this->_model = $helper->getObject( \Magento\Customer\Model\Customer::class, @@ -146,7 +156,8 @@ protected function setUp() 'accountConfirmation' => $this->accountConfirmation, '_addressesFactory' => $this->addressesFactory, 'customerDataFactory' => $this->customerDataFactory, - 'dataObjectHelper' => $this->dataObjectHelper + 'dataObjectHelper' => $this->dataObjectHelper, + 'mathRandom' => $this->mathRandom, ] ); } @@ -219,15 +230,17 @@ public function testSendNewAccountEmailWithoutStoreId() ->method('getTransport') ->will($this->returnValue($transportMock)); - $this->_model->setData([ - 'website_id' => 1, - 'store_id' => 1, - 'email' => 'email@example.com', - 'firstname' => 'FirstName', - 'lastname' => 'LastName', - 'middlename' => 'MiddleName', - 'prefix' => 'Name Prefix', - ]); + $this->_model->setData( + [ + 'website_id' => 1, + 'store_id' => 1, + 'email' => 'email@example.com', + 'firstname' => 'FirstName', + 'lastname' => 'LastName', + 'middlename' => 'MiddleName', + 'prefix' => 'Name Prefix', + ] + ); $this->_model->sendNewAccountEmail('registered'); } @@ -383,4 +396,20 @@ public function testGetDataModel() $this->_model->getDataModel(); $this->assertEquals($customerDataObject, $this->_model->getDataModel()); } + + /** + * Check getRandomConfirmationKey use cryptographically secure function + * + * @return void + */ + public function testGetRandomConfirmationKey() : void + { + $this->mathRandom + ->expects($this->once()) + ->method('getRandomString') + ->with(32) + ->willReturn('random_string'); + + $this->_model->getRandomConfirmationKey(); + } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php index 05953b09b8c04..8032399e14881 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php @@ -107,7 +107,7 @@ protected function setUp() $this->createMock(\Magento\Customer\Model\ResourceModel\Customer::class); $this->customerRegistry = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); $this->dataObjectHelper = $this->createMock(\Magento\Framework\Api\DataObjectHelper::class); - $this->customerFactory = + $this->customerFactory = $this->createPartialMock(\Magento\Customer\Model\CustomerFactory::class, ['create']); $this->customerSecureFactory = $this->createPartialMock( \Magento\Customer\Model\Data\CustomerSecureFactory::class, @@ -193,9 +193,10 @@ protected function setUp() public function testSave() { $customerId = 1; - $storeId = 2; - $customerModel = $this->createPartialMock(\Magento\Customer\Model\Customer::class, [ + $customerModel = $this->createPartialMock( + \Magento\Customer\Model\Customer::class, + [ 'getId', 'setId', 'setStoreId', @@ -210,7 +211,8 @@ public function testSave() 'setFirstFailure', 'setLockExpires', 'save', - ]); + ] + ); $origCustomer = $this->customer; @@ -229,14 +231,17 @@ public function testSave() 'setAddresses' ] ); - $customerSecureData = $this->createPartialMock(\Magento\Customer\Model\Data\CustomerSecure::class, [ - 'getRpToken', - 'getRpTokenCreatedAt', - 'getPasswordHash', - 'getFailuresNum', - 'getFirstFailure', - 'getLockExpires', - ]); + $customerSecureData = $this->createPartialMock( + \Magento\Customer\Model\Data\CustomerSecure::class, + [ + 'getRpToken', + 'getRpTokenCreatedAt', + 'getPasswordHash', + 'getFailuresNum', + 'getFirstFailure', + 'getLockExpires', + ] + ); $this->customer->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); @@ -268,17 +273,6 @@ public function testSave() $customerModel->expects($this->once()) ->method('getStoreId') ->willReturn(null); - $store = $this->createMock(\Magento\Store\Model\Store::class); - $store->expects($this->once()) - ->method('getId') - ->willReturn($storeId); - $this->storeManager - ->expects($this->once()) - ->method('getStore') - ->willReturn($store); - $customerModel->expects($this->once()) - ->method('setStoreId') - ->with($storeId); $customerModel->expects($this->once()) ->method('setId') ->with($customerId); @@ -310,16 +304,20 @@ public function testSave() $customerModel->expects($this->once()) ->method('setRpToken') - ->willReturnMap([ + ->willReturnMap( + [ ['rpToken', $customerModel], [null, $customerModel], - ]); + ] + ); $customerModel->expects($this->once()) ->method('setRpTokenCreatedAt') - ->willReturnMap([ + ->willReturnMap( + [ ['rpTokenCreatedAt', $customerModel], [null, $customerModel], - ]); + ] + ); $customerModel->expects($this->once()) ->method('setPasswordHash') @@ -371,32 +369,37 @@ public function testSave() public function testSaveWithPasswordHash() { $customerId = 1; - $storeId = 2; $passwordHash = 'ukfa4sdfa56s5df02asdf4rt'; - $customerSecureData = $this->createPartialMock(\Magento\Customer\Model\Data\CustomerSecure::class, [ - 'getRpToken', - 'getRpTokenCreatedAt', - 'getPasswordHash', - 'getFailuresNum', - 'getFirstFailure', - 'getLockExpires', - ]); + $customerSecureData = $this->createPartialMock( + \Magento\Customer\Model\Data\CustomerSecure::class, + [ + 'getRpToken', + 'getRpTokenCreatedAt', + 'getPasswordHash', + 'getFailuresNum', + 'getFirstFailure', + 'getLockExpires', + ] + ); $origCustomer = $this->customer; - $customerModel = $this->createPartialMock(\Magento\Customer\Model\Customer::class, [ - 'getId', - 'setId', - 'setStoreId', - 'getStoreId', - 'getAttributeSetId', - 'setAttributeSetId', - 'setRpToken', - 'setRpTokenCreatedAt', - 'getDataModel', - 'setPasswordHash', - 'save', - ]); + $customerModel = $this->createPartialMock( + \Magento\Customer\Model\Customer::class, + [ + 'getId', + 'setId', + 'setStoreId', + 'getStoreId', + 'getAttributeSetId', + 'setAttributeSetId', + 'setRpToken', + 'setRpTokenCreatedAt', + 'getDataModel', + 'setPasswordHash', + 'save', + ] + ); $customerAttributesMetaData = $this->getMockForAbstractClass( \Magento\Framework\Api\CustomAttributesDataInterface::class, [], @@ -447,7 +450,6 @@ public function testSaveWithPasswordHash() $customerSecureData->expects($this->once()) ->method('getLockExpires') ->willReturn('lockExpires'); - $this->customer->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); @@ -477,20 +479,6 @@ public function testSaveWithPasswordHash() ->method('create') ->with(['data' => ['customerData']]) ->willReturn($customerModel); - $customerModel->expects($this->once()) - ->method('getStoreId') - ->willReturn(null); - $store = $this->createMock(\Magento\Store\Model\Store::class); - $store->expects($this->once()) - ->method('getId') - ->willReturn($storeId); - $this->storeManager - ->expects($this->once()) - ->method('getStore') - ->willReturn($store); - $customerModel->expects($this->once()) - ->method('setStoreId') - ->with($storeId); $customerModel->expects($this->once()) ->method('setId') ->with($customerId); diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php index dfc9af2a35498..d917cc4908ac8 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -7,6 +7,9 @@ use Magento\Customer\Ui\Component\ColumnFactory; +/** + * Test ColumnFactory Class + */ class ColumnFactoryTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */ diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php index a0681ce6e94a5..f3c0a56262622 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php @@ -7,6 +7,9 @@ use Magento\Customer\Ui\Component\FilterFactory; +/** + * Test FilterFactory Class + */ class FilterFactoryTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */ diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php index 187f385bc9107..c12dec865cde8 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/AttributeRepositoryTest.php @@ -7,6 +7,9 @@ use Magento\Customer\Ui\Component\Listing\AttributeRepository; +/** + * Test AttributeRepository Class + */ class AttributeRepositoryTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Customer\Api\CustomerMetadataManagementInterface|\PHPUnit_Framework_MockObject_MockObject */ diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php index 056c7e71e1827..4a16acd98d827 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php @@ -7,6 +7,9 @@ use Magento\Customer\Ui\Component\Listing\Column\Actions; +/** + * Class ActionsTest + */ class ActionsTest extends \PHPUnit\Framework\TestCase { /** @var Actions */ @@ -64,7 +67,8 @@ public function testPrepareDataSource() 'edit' => [ 'href' => 'http://magento.com/customer/index/edit', 'label' => new \Magento\Framework\Phrase('Edit'), - 'hidden' => false + 'hidden' => false, + '__disableTmpl' => true, ] ] ], diff --git a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php index 8cdf53f35387e..cb66dc3db7c77 100644 --- a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php @@ -68,14 +68,19 @@ public function __construct( */ public function create(array $attributeData, $columnName, $context, array $config = []) { - $config = array_merge([ - 'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]), - 'dataType' => $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]), - 'align' => 'left', - 'visible' => (bool)$attributeData[AttributeMetadata::IS_VISIBLE_IN_GRID], - 'component' => $this->getJsComponent($this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT])), - '__disableTmpl' => 'true' - ], $config); + $config = array_merge( + [ + 'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]), + 'dataType' => $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]), + 'align' => 'left', + 'visible' => (bool)$attributeData[AttributeMetadata::IS_VISIBLE_IN_GRID], + 'component' => $this->getJsComponent( + $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]) + ), + '__disableTmpl' => 'true' + ], + $config + ); if ($attributeData[AttributeMetadata::FRONTEND_INPUT] == 'date') { $config['dateFormat'] = 'MMM d, y'; $config['timezone'] = false; diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php index d6a4067ef3db6..9441beeb7dc61 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php @@ -60,6 +60,7 @@ public function prepareDataSource(array $dataSource) ), 'label' => __('Edit'), 'hidden' => false, + '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php index 615ad2243a467..61cb06cf77e0d 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/Group/Options.php @@ -44,9 +44,12 @@ public function toOptionArray() $this->options = $this->collectionFactory->create()->toOptionArray(); } - array_walk($this->options, function (&$item) { - $item['__disableTmpl'] = true; - }); + array_walk( + $this->options, + function (&$item) { + $item['__disableTmpl'] = true; + } + ); return $this->options; } diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php index 6870bd1136d10..12f6f2705125b 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php @@ -79,6 +79,7 @@ public function prepareDataSource(array $dataSource) ] ), 'label' => __('Edit'), + '__disableTmpl' => true ], ]; @@ -102,7 +103,8 @@ public function prepareDataSource(array $dataSource) $this->escaper->escapeJs($title) ) ], - 'post' => true + 'post' => true, + '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index d0edb65342329..b1a313f7b45d2 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "102.0.*", "magento/module-authorization": "100.3.*", "magento/module-backend": "101.0.*", @@ -44,5 +44,5 @@ "Magento\\Customer\\": "" } }, - "version": "102.0.2" + "version": "102.0.3" } diff --git a/app/code/Magento/Customer/etc/acl.xml b/app/code/Magento/Customer/etc/acl.xml index 1d45aa6445db8..1583c190d5c06 100644 --- a/app/code/Magento/Customer/etc/acl.xml +++ b/app/code/Magento/Customer/etc/acl.xml @@ -26,7 +26,7 @@ <resource id="Magento_Customer::config_customer" title="Customers Section" translate="title" sortOrder="50" /> </resource> </resource> - </resource> + </resource> </resource> </resources> </acl> diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv index 1d4193d1b1ea8..3495feb925cb3 100644 --- a/app/code/Magento/Customer/i18n/en_US.csv +++ b/app/code/Magento/Customer/i18n/en_US.csv @@ -47,7 +47,7 @@ Sending,Sending Paused,Paused View,View Unknown,Unknown -Order,Order +"Order #","Order #" Purchased,Purchased "Bill-to Name","Bill-to Name" "Ship-to Name","Ship-to Name" diff --git a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml b/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml index 23b5718870229..e982d8bb0960e 100644 --- a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml +++ b/app/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml @@ -15,5 +15,8 @@ <referenceContainer name="content"> <uiComponent name="customer_form"/> </referenceContainer> + <referenceContainer name="after.body.start"> + <block class="Magento\Catalog\Block\Adminhtml\Product\Composite\Configure" name="after.body.start.product_composite_configure" template="Magento_Catalog::catalog/product/composite/configure.phtml"/> + </referenceContainer> </body> </page> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml b/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml index 143b4be507af9..14aa3f4763e6c 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/edit/js.phtml @@ -3,7 +3,4 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml b/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml index f55562682d9be..b792bc27f5b64 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/sales/order/create/address/form/renderer/vat.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Customer\Block\Adminhtml\Sales\Order\Address\Form\Renderer\Vat $block */ $_element = $block->getElement(); @@ -13,16 +11,16 @@ $_note = $_element->getNote(); $_class = $_element->getFieldsetHtmlClass(); $_validateButton = $block->getValidateButton(); ?> -<?php if (!$_element->getNoDisplay()): ?> +<?php if (!$_element->getNoDisplay()) : ?> <div class="admin__field field-vat-number"> - <?php if ($_element->getType() == 'hidden'): ?> + <?php if ($_element->getType() == 'hidden') : ?> <div class="hidden"><?= $_element->getElementHtml() ?></div> - <?php else: ?> + <?php else : ?> <?= $_element->getLabelHtml() ?> <div class="admin__field-control <?= /* @noEscape */ $_element->hasValueClass() ? $block->escapeHtmlAttr($_element->getValueClass()) : 'value' ?><?= $_class ? $block->escapeHtmlAttr($_class) . '-value' : '' ?>"> <?= $_element->getElementHtml() ?> - <?php if ($_note): ?> - <div class="admin__field-note<?= $_class ? " {$_class}-note" : '' ?>" id="note_<?= $block->escapeHtmlAttr($_element->getId()) ?>"> + <?php if ($_note) : ?> + <div class="admin__field-note<?= /* @noEscape */ $_class ? " {$block->escapeHtmlAttr($_class)}-note" : '' ?>" id="note_<?= $block->escapeHtmlAttr($_element->getId()) ?>"> <span><?= $block->escapeHtml($_note) ?></span> </div> <?php endif; ?> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml b/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml index 8eb3057d0a390..ab1671ede6e8a 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/system/config/validatevat.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Customer\Block\Adminhtml\System\Config\Validatevat $block */ ?> <script> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml index 76fa53c12548d..434e5606cd032 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart.phtml @@ -4,11 +4,9 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /* @var \Magento\Customer\Block\Adminhtml\Edit\Tab\Cart $block */ ?> -<?php if ($block->getCartHeader()): ?> +<?php if ($block->getCartHeader()) : ?> <div class="content-header skip-header"> <table> <tr> @@ -19,76 +17,74 @@ <?php endif ?> <?= $block->getGridParentHtml() ?> <?php if ($block->canDisplayContainer()) : ?> -<?php - $listType = $block->getJsObjectName(); -?> -<script> -require([ - "Magento_Ui/js/modal/alert", - "Magento_Ui/js/modal/confirm", - "Magento_Catalog/catalog/product/composite/configure" -], function(alert, confirm){ + <?php $listType = $block->getJsObjectName(); ?> + <script> + require([ + "Magento_Ui/js/modal/alert", + "Magento_Ui/js/modal/confirm", + "Magento_Catalog/catalog/product/composite/configure" + ], function(alert, confirm){ -<?= $block->escapeJs($block->getJsObjectName()) ?>cartControl = { - reload: function (params) { - if (!params) { - params = {}; - } - <?= $block->escapeJs($block->getJsObjectName()) ?>.reloadParams = params; - <?= $block->escapeJs($block->getJsObjectName()) ?>.reload(); - <?= $block->escapeJs($block->getJsObjectName()) ?>.reloadParams = {}; - }, + <?= $block->escapeJs($block->getJsObjectName()) ?>cartControl = { + reload: function (params) { + if (!params) { + params = {}; + } + <?= $block->escapeJs($block->getJsObjectName()) ?>.reloadParams = params; + <?= $block->escapeJs($block->getJsObjectName()) ?>.reload(); + <?= $block->escapeJs($block->getJsObjectName()) ?>.reloadParams = {}; + }, - configureItem: function (itemId) { - productConfigure.setOnLoadIFrameCallback('<?= $block->escapeJs($listType) ?>', this.cbOnLoadIframe.bind(this)); - productConfigure.showItemConfiguration('<?= $block->escapeJs($listType) ?>', itemId); - return false; - }, + configureItem: function (itemId) { + productConfigure.setOnLoadIFrameCallback('<?= $block->escapeJs($listType) ?>', this.cbOnLoadIframe.bind(this)); + productConfigure.showItemConfiguration('<?= $block->escapeJs($listType) ?>', itemId); + return false; + }, - cbOnLoadIframe: function (response) { - if (!response.ok) { - return; - } - this.reload(); - }, + cbOnLoadIframe: function (response) { + if (!response.ok) { + return; + } + this.reload(); + }, - removeItem: function (itemId) { - var self = this; + removeItem: function (itemId) { + var self = this; - if (!itemId) { - alert({ - content: '<?= $block->escapeJs(__('No item specified.')) ?>' - }); + if (!itemId) { + alert({ + content: '<?= $block->escapeJs(__('No item specified.')) ?>' + }); - return false; - } + return false; + } - confirm({ - content: '<?= $block->escapeJs(__('Are you sure you want to remove this item?')) ?>', - actions: { - confirm: function(){ - self.reload({'delete':itemId}); + confirm({ + content: '<?= $block->escapeJs(__('Are you sure you want to remove this item?')) ?>', + actions: { + confirm: function(){ + self.reload({'delete':itemId}); + } } - } - }); - } -}; + }); + } + }; -<?php -$params = [ - 'customer_id' => $block->getCustomerId(), - 'website_id' => $block->getWebsiteId(), -]; -?> -productConfigure.addListType( - '<?= $block->escapeJs($listType) ?>', - { - urlFetch: '<?= $block->escapeJs($block->escapeUrl($block->getUrl('customer/cart_product_composite_cart/configure', $params))) ?>', - urlConfirm: '<?= $block->escapeJs($block->escapeUrl($block->getUrl('customer/cart_product_composite_cart/update', $params))) ?>' - } -); + <?php + $params = [ + 'customer_id' => $block->getCustomerId(), + 'website_id' => $block->getWebsiteId(), + ]; + ?> + productConfigure.addListType( + '<?= $block->escapeJs($listType) ?>', + { + urlFetch: '<?= $block->escapeJs($block->escapeUrl($block->getUrl('customer/cart_product_composite_cart/configure', $params))) ?>', + urlConfirm: '<?= $block->escapeJs($block->escapeUrl($block->getUrl('customer/cart_product_composite_cart/update', $params))) ?>' + } + ); -}); -</script> + }); + </script> <?php endif ?> <br /> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/newsletter.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/newsletter.phtml index 30acb16c158d2..12d4902fb1892 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/tab/newsletter.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/newsletter.phtml @@ -3,9 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - ?> <div class="entry-edit"> <?= $block->getForm()->getHtml() ?> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml index 2bf1e0c32112f..a0e7a2faefbab 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Customer\Block\Adminhtml\Edit\Tab\View\PersonalInfo $block */ $lastLoginDateAdmin = $block->getLastLoginDate(); @@ -26,7 +24,7 @@ $allowedAddressHtmlTags = ['b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub <th><?= $block->escapeHtml(__('Last Logged In:')) ?></th> <td><?= $block->escapeHtml($lastLoginDateAdmin) ?> (<?= $block->escapeHtml($block->getCurrentStatus()) ?>)</td> </tr> - <?php if ($lastLoginDateAdmin != $lastLoginDateStore): ?> + <?php if ($lastLoginDateAdmin != $lastLoginDateStore) : ?> <tr> <th><?= $block->escapeHtml(__('Last Logged In (%1):', $block->getStoreLastLoginDateTimezone())) ?></th> <td><?= $block->escapeHtml($lastLoginDateStore) ?> (<?= $block->escapeHtml($block->getCurrentStatus()) ?>)</td> @@ -44,7 +42,7 @@ $allowedAddressHtmlTags = ['b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub <th><?= $block->escapeHtml(__('Account Created:')) ?></th> <td><?= $block->escapeHtml($createDateAdmin) ?></td> </tr> - <?php if ($createDateAdmin != $createDateStore): ?> + <?php if ($createDateAdmin != $createDateStore) : ?> <tr> <th><?= $block->escapeHtml(__('Account Created on (%1):', $block->getStoreCreateDateTimezone())) ?></th> <td><?= $block->escapeHtml($createDateStore) ?></td> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml index 12eae5cac9b1a..7b888c040463f 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/sales.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Sales $block */ $singleStoreMode = $block->isSingleStoreMode(); @@ -19,7 +17,7 @@ $singleStoreMode = $block->isSingleStoreMode(); <table class="data-table"> <thead> <tr> - <?php if (!$singleStoreMode): ?> + <?php if (!$singleStoreMode) : ?> <th><?= $block->escapeHtml(__('Web Site')) ?></th> <th><?= $block->escapeHtml(__('Store')) ?></th> <th><?= $block->escapeHtml(__('Store View')) ?></th> @@ -28,7 +26,7 @@ $singleStoreMode = $block->isSingleStoreMode(); <th class="last"><?= $block->escapeHtml(__('Average Sale')) ?></th> </tr> </thead> - <?php if (!$singleStoreMode): ?> + <?php if (!$singleStoreMode) : ?> <tfoot> <tr> <td colspan="3"><strong><?= $block->escapeHtml(__('All Store Views')) ?></strong></td> @@ -37,40 +35,40 @@ $singleStoreMode = $block->isSingleStoreMode(); </tr> </tfoot> <?php endif; ?> - <?php if ($block->getRows()): ?> + <?php if ($block->getRows()) : ?> <tbody> <?php $_i = 0; ?> - <?php foreach ($block->getRows() as $_websiteId => $_groups): ?> - <?php $_websiteRow = false; ?> - <?php foreach ($_groups as $_groupId => $_stores): ?> - <?php $_groupRow = false; ?> - <?php foreach ($_stores as $_row): ?> - <?php if (!$singleStoreMode): ?> - <?php if ($_row->getStoreId() == 0): ?> - <td colspan="3"><?= $block->escapeHtml($_row->getStoreName()) ?></td> - <?php else: ?> - <tr<?= ($_i++ % 2 ? ' class="even"' : '') ?>> - <?php if (!$_websiteRow): ?> - <td rowspan="<?= $block->escapeHtmlAttr($block->getWebsiteCount($_websiteId)) ?>"><?= $block->escapeHtml($_row->getWebsiteName()) ?></td> - <?php $_websiteRow = true; ?> + <?php foreach ($block->getRows() as $_websiteId => $_groups) : ?> + <?php $_websiteRow = false; ?> + <?php foreach ($_groups as $_groupId => $_stores) : ?> + <?php $_groupRow = false; ?> + <?php foreach ($_stores as $_row) : ?> + <?php if (!$singleStoreMode) : ?> + <?php if ($_row->getStoreId() == 0) : ?> + <td colspan="3"><?= $block->escapeHtml($_row->getStoreName()) ?></td> + <?php else : ?> + <tr<?= ($_i++ % 2 ? ' class="even"' : '') ?>> + <?php if (!$_websiteRow) : ?> + <td rowspan="<?= $block->escapeHtmlAttr($block->getWebsiteCount($_websiteId)) ?>"><?= $block->escapeHtml($_row->getWebsiteName()) ?></td> + <?php $_websiteRow = true; ?> <?php endif; ?> - <?php if (!$_groupRow): ?> - <td rowspan="<?= count($_stores) ?>"><?= $block->escapeHtml($_row->getGroupName()) ?></td> - <?php $_groupRow = true; ?> + <?php if (!$_groupRow) : ?> + <td rowspan="<?= count($_stores) ?>"><?= $block->escapeHtml($_row->getGroupName()) ?></td> + <?php $_groupRow = true; ?> <?php endif; ?> - <td><?= $block->escapeHtml($_row->getStoreName()) ?></td> + <td><?= $block->escapeHtml($_row->getStoreName()) ?></td> <?php endif; ?> - <?php else: ?> - <tr> + <?php else : ?> + <tr> <?php endif; ?> - <td><?= $block->escapeHtml($block->formatCurrency($_row->getLifetime(), $_row->getWebsiteId())) ?></td> - <td><?= $block->escapeHtml($block->formatCurrency($_row->getAvgsale(), $_row->getWebsiteId())) ?></td> - </tr> + <td><?= $block->escapeHtml($block->formatCurrency($_row->getLifetime(), $_row->getWebsiteId())) ?></td> + <td><?= $block->escapeHtml($block->formatCurrency($_row->getAvgsale(), $_row->getWebsiteId())) ?></td> + </tr> <?php endforeach; ?> <?php endforeach; ?> <?php endforeach; ?> </tbody> - <?php else: ?> + <?php else : ?> <tbody> <tr class="hidden"><td colspan="<?= /* @noEscape */ $singleStoreMode ? 2 : 5 ?>"></td></tr> </tbody> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml index 35f0ed8f1dae2..692cb2ecb964d 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml @@ -215,7 +215,6 @@ <target>${ $.provider }:${ $.parentScope }.country_id</target> </filterBy> <customEntry>region</customEntry> - <options class="Magento\Directory\Model\ResourceModel\Region\Collection"/> </settings> </select> </formElements> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml index f845d407d401a..97ae9a9953eb6 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml @@ -157,18 +157,12 @@ <column name="billing_telephone" sortOrder="60"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">Phone</label> </settings> </column> <column name="billing_postcode" sortOrder="70"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">ZIP</label> </settings> </column> @@ -269,9 +263,6 @@ <column name="billing_city" sortOrder="210"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">City</label> <visible>false</visible> </settings> @@ -279,9 +270,6 @@ <column name="billing_fax" sortOrder="220"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">Fax</label> <visible>false</visible> </settings> @@ -289,9 +277,6 @@ <column name="billing_vat_id" sortOrder="230"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">VAT Number</label> <visible>false</visible> </settings> @@ -299,9 +284,6 @@ <column name="billing_company" sortOrder="240"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">Company</label> <visible>false</visible> </settings> @@ -309,9 +291,6 @@ <column name="billing_firstname" sortOrder="250"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">Billing Firstname</label> <visible>false</visible> </settings> @@ -319,9 +298,6 @@ <column name="billing_lastname" sortOrder="260"> <settings> <filter>text</filter> - <editor> - <editorType>text</editorType> - </editor> <label translate="true">Billing Lastname</label> <visible>false</visible> </settings> diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index e87997dbdb5e9..5fb8b17dbb8c5 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -129,7 +129,7 @@ </validation> <dataType>number</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> <description translate="true">If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site.</description> </tooltip> <imports> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_account_forgotpassword.xml b/app/code/Magento/Customer/view/frontend/layout/customer_account_forgotpassword.xml index 9a701c14a0307..24cede5f0232a 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_account_forgotpassword.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_account_forgotpassword.xml @@ -7,7 +7,7 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <head> - <title>Forgot Your Password + Forgot Your Password? diff --git a/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml b/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml index ca7393f2129e0..0d4cf3c721d14 100644 --- a/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/account/authentication-popup.phtml @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** @var \Magento\Customer\Block\Account\AuthenticationPopup $block */ ?>